|
Foundation |
|
| |||||||||||
PREV PACKAGE NEXT PACKAGE | FRAMES NO FRAMES |
See:
Description
Interface Summary | |
---|---|
ChildRepositoryInterface | |
ChildServerInterface | |
DAVRepositoryInterface | This interface extends RepositoryInterface. |
FilterInterface | The FilterInterface describes a means for setting and getting information used to limit the results of a search against a repository. |
InformationServiceInterface | The InformationService handles finding repositories, connecting a user context to suitable repositories for the identities it contains, and trans-repository searching. |
MetadataChangeListener | This interface describes a listener for MetadataChangeEvent events. |
MetadataRepositoryInterface | |
MetadataServerInterface | |
OMIRepositoryInterface | This interface extends RepositoryInterface. |
ProtocolInterface | Information protocol constants. |
RepositoryInterface | The Repository is a single persistent store for metadata used by client applications. |
RepositoryListenerInterface | This interface is to allow classes to register with repositories as repository event listeners. |
ServerInterface |
Class Summary | |
---|---|
AbstractRepository | |
ACTEntry | |
Authorization | This class is output from the OMIRepositoryInterface.getAuthorizationsOnObj method and input for OMIRepositoryInterface.setAuthorizationsOnObj. |
AuthorizationObjectsResult | |
AuthResult | |
ContentInfo | This is a simple, immutable class for representing the content information for a folder. |
DAVChildRepository | DAV child repository. |
DAVRepository | The DAV Repository is a single persistent store for information used by client applications. |
Factory | The Factory is a class that takes repository-specific objects and turns them into "smart objects". |
FactoryAction | An Information Service factory's action (type, class name, and optionally method name) and optional filter. |
Filter | The Filter class describes a means for setting and getting information used to limit the results of a search against a repository. |
FilterComponent | The FilterComponent is used to build a filter to use to search a repository. |
IdentityInfo | |
InformationService | The Information Service handles finding repositories, connecting a user context to suitable repositories for the identities it contains, and federated searching across repositories. |
InternalLoginSitePolicies | |
InternalLoginUserInfo | |
MetadataChangeEvent | This event is used by the repository instances to keep their caches synchronized. |
OMIChildServer | |
OMIServer | This class is the OMI implementation of the ServerInterface. |
OMIServerChildRepository | |
OMIServerRepository | This class interfaces with an OMI repository using the jOMA classes (com.sas.metadata.remote) as the repository-specific layer. |
RepositoryDef | This class holds a definition for a repository connection. |
RepositoryEvent | This class represents an event that occurred in a repository while attempting an operation against it. |
RepositoryGroup | Repository group. |
RepositoryInfo | |
ServerDef | Server definition used by an Information Service configuration. |
UserFolderType |
Exception Summary | |
---|---|
AutoConnectException | An exception class used to report failure to connect to a repository defined as auto-connect. |
EntityHasChangedException | This exception is thrown when during an update operation the entity is already locked or already changed since we retrieved a copy of the entity. |
FactoryClassException | This exception is thrown when a NoClassDefFound exception is thrown during smart object creation in the Information Service Factory. |
FactoryException | This Exception is thrown when an error occurs in the Information Service Factory which instantiates "Smart objects" from metadata retrieved from repositories. |
FactoryNotInitializedException | |
FilterNotSupportedException | |
ItemAlreadyExistsException | Thrown when an item already exists. |
NewInstanceException | |
PartialFolderResultsException | This Exception gets thrown if an Information Services search is performed which may return multiple objects, and at least one exception is thrown while retrieving metadata. |
PartialListResultsException | This Exception gets thrown if an Information Services search is performed which may return multiple objects, and at least one exception is thrown while retrieving metadata. |
PartialResultsException | This Exception gets thrown if multiple objects were expected in a result set, and one or more exceptions were thrown during metadata retrieval. |
RepositoryUrlMismatchException | |
ServerVersionException | |
VersionIncompatibleException | This exception will get thrown when an attempt is made to instantiate a smart object with data that is version incompatible with the implementation. |
Interact with data repositories on behalf of client applications. The Information Service abstracts various information service sources including SAS Metadata Server, LDAP, and WEBDAV.
The information service provides convenient access to data repositories. It will connect to multiple repositories and perform searches that span repositories.
Entity | An unit of information from a persistent store. |
Filter | A means of restricting search results to an interesting set of items |
Repository | A persistent storage mechanism |
Classes in this package include a filtering mechanism that will allow creating a single filter to search disparate repositories, but will allow repository-specific information so that efficient searching can be achieved.
The Information Service provides methods for connecting to repositories by repository definition (a method exists to get these definitions as well) or by name. It also provides methods to search all repositories connected to by a user and aggregate the results, fetch specific objects, and create new metadata objects.
There are also methods for searching multiple repositories. All repositories a user is connected to can be searched, or only repositories using a specific protocol (such as "dav"). A single filter, or a filter array can be provided to limit the result set being returned.
Search results that are returned from the repositories are wrapped by
classes that implment the
com.sas.services.information.MetadataInterface
interface.
This provides a common means for accessing basic information about the
object so it can be rendered and processed. Commonly used objects
will have a specific subclass which implments a functional interface
which should hide some of the data model specifics. It should also
provide repository neutrality for classes that may be contained in
more than one type of backing store. For instance, a stored process
may be stored in SAS Metadata Server as a ClassifierMap, and in LDAP as a
SasStoredProcess. A
com.sas.services.information.metadata.StoredProcess
class
hides these differences and provides accessor methods for keywords,
servers which can execute it, and path information. All of this
without having to know the underlying model, or having to know which
type of repository the data came from. The StoredProcess class also
has a method for interacting with the Stored Process Foundation Service
to create a new instance of a service object which can then be
executed.
Every Metadata object provides an entity key that can be used to
determine the type of the object and locate it in the repository at a
later date, if desired. The entity key is composed of a
repository-neutral type, if defined, a '+' character, a url that has
the protocol, server, port, repository ID and the repository type.
This key may be stored as a bookmark so the item can be referenced
at a later date.
Example: Channel+omi://d5296.us.sas.com:4059/A5CK95WU.AB000009/ITChannel
To make the cross-repository search as generic as possible, the filter
mechanism is repository neutral. The client may set the type to
search for, and create filter components that limit the result
set to those items it is interested in.
Filter components are relationship tests to perform on repository
entities to limit the results of a search. Components can have
subcomponents to build complex search filters.
Filter components have two aggregation modes: ALL_OF and ANY_OF.
These determine how subscomponents are used. If the aggregation mode
is ALL_OF, all of the subcomponent tests must be true for the entity
to pass. If ANY_OF is used, if any of the tests is true, the entity
passes the filter.
There are four relationship checks: EQUALS, CONTAINS, STARTSWITH, and
ENDSWITH. For a component with a given key and value setting, an
entity will pass the test if it has an attribute that matches the key,
and:
If the relationship is EQUALS, the attribute has a value that equals
the value in the component.
If the relationship is CONTAINS, the attribute has a value that
contains the value in the component.
If the relationship is STARTSWITH, the attribute has a value that
starts with the value in the component.
If the relationship is ENDSWITH, the attribute has a value that ends
with the value in the component.
The Filter class can generate search strings appropriate to the known
repositories, or in a generic format that can be parsed and used to
produce search filters for customer-specific repositories.
Filter simple = new Filter( "subject", FilterComponent.CONTAINS, "Sales" );
|
Filter complex = new Filter();
|
This mechanism works the same regardless of the repository that's
being searched. However, some searches can be made much more
efficiently if certain repository-specific options are provided. So
the filter allows those options to be specified if the search client
knows what repository is being searched, and what options to provide
to improve that search. For instance, the Filter allows the client
to set the specific SAS Metadata Server repository to search, a template to use to
retrieve data, and the flags to pass to the SAS Metadata Server to use when
processing the request. For LDAP, a search base can be provided to
limit the scope of the search.
It is also useful to create type-specific subclasses of the Filter
class that can be used to search for given metadata types. Rather
than building the filter components manually, you can set the
desired attributes and the Filter object will build the appropriate
filter components and generate the correct search string. For
instance, there is a StoredProcessFilter class that allows a user
to search for Stored Process objects by keyword or name. Just
create a new StoredProcessFilter and set the name, or add keywords
to search for and pass it to the search method.
A repository is a persistent storage mechanism for metadata and/or content. It is typically a server that implements a protocol for providing access to its contents (e.g., SAS Metadata Server, LDAP, or WebDAV). The Information Service provides methods for connecting to a repository, searching for repository definitions, and searching the connected repositories for information.
Most repositories, besides providing access to stored information,
also implement some sort of authentication mechanism. The typical
installation will, presumably, use one of these repositories as the
primary authentication mechanism. The User Service, Authentication
Service and the Information Service work together during the
authentication process so that after a successful authentication, the
User Context is created and has a handle to the repository(s) that the
user authenticated with. From this starting point, the Information
Service can be used to look up other repositories that the user may
have access to using the findServers
method, which locates
definitions for servers that implement one of the known protocols (from
the repository configuration). The result of this call is a set
of repository definitions that can then be passed into the
connect
method to try to establish a connection to the
server.
The primary use for the Information Service is to provide a federated
search mechanism for any repositories a user has a connection to. The
method takes as input, the UserContext for the user who is doing the
search, and a filter. The connected repository handles are searched
using the filter and the results are collected and returned. All of
the results will implement the MetdataInterface
. Since a
search can return multiple types from different repositories, it's up
to the application to determine what specific type of entry each item
is and process it accordingly. Through the MetadataInterface, the
application has access to the object type, name and description.
The Information Service also provides a convenience method for
fetching an item using the entity key. It gets the url from the key,
and looks through the connected repository handles to see if there's a
connection to the server that contains the item. If so, it uses the
repository to fetch the item and return the resulting
MetdataInterface
object for it. Currently, the Information
Service will not try to establish a connection to a server in this
method if one does not already exist, but that function may be added
at a future date.
The repositories also have methods for adding items to the store,
and deleting items. Since these functions necessarily only apply
to a single repository instance, they aren't implmented in the
Information Service. The update function is handled through the
update
method of the object being updated.
login.config/** Login Configuration for the Foundation Services Sample Application **/ PFS { com.sas.services.security.LdapLoginModule optional "host"="d5296.us.sas.com" "port"="389" "privilegedUser"="cn=sasidb,o=SAS Institute,c=US" "privilegedPassword"="sasidb1" "base"="o=SAS Institute,c=US" "groupbase"="ou=Groups,o=SAS Institute,c=US" "loginbase"="o=SAS Institute,c=US" "domain"="ldap"; com.sas.services.security.OMILoginModule optional "host"="d5296.us.sas.com" "port"="4059" "repository"="A0000001.A5CK95WU" "domain"="CARYNT"; }; |
UserContextInterface userMe = _userService.newUser( "sasgwi", "sasgwi1", "ldap" ); |
If the authentication with either or both modules is successful, a handle to the repository that was connected to will be saved in the UserContext. Also, any credentials that are in the repository that are available to the user will be retrieved and saved in the UserContext.
/* * Look through the connected repositories for other * repositories. Their definitions will be returned in the * iterator. Go through the iterator and try to connect to * any servers that were found. */ Iterator serverIter = _informationService.findServers( userMe ).iterator(); while( serverIter.hasNext() ) { RepositoryDef reposDef = (RepositoryDef)serverIter.next(); if ( ! (_informationService.connect( userMe, reposDef ) ) ) { System.out.println( "connection to: " + reposDef.getProtocol() + "://" + reposDef.getHost() + ":" + reposDef.getPort() + " failed" ); } else { System.out.println( "connection to: " + reposDef.getProtocol() + "://" + reposDef.getHost() + ":" + reposDef.getPort() + " succeeded" ); } } |
/* * Create a simple filter and perform a search. */ Filter filter = new ChannelFilter(); filter.setLdapBase( "cn=SAS,o=SAS Institute,c=US" ); it = _informationService.search( userMe, filter ).iterator(); while( it.hasNext() ) { Object o = it.next(); if ( o instanceof com.sas.services.information.metadata.ChannelInterface ) { com.sas.services.information.metadata.ChannelInterface channel = (com.sas.services.information.metadata.ChannelInterface) o; if ( channel.getName().equals("garytest") ) { channel.setDescription( "Test for Gary" ); channel.update(); } System.out.println( "Found " + channel.getEntityKey() + "." + channel.getTrackingId() ); } } |
Results Found Channel+ldap://d5296.us.sas.com:389/saschannelcn=Product Development, cn=saschannels,sascomponent=sasPublishSubscribe,cn=SAS,o=SAS Institute,c=US/saschannel.5 Found Channel+ldap://d5296.us.sas.com:389/saschannelcn=Quality Assurance, cn=saschannels,sascomponent=sasPublishSubscribe,cn=SAS,o=SAS Institute,c=US/saschannel.6 Found Channel+ldap://d5296.us.sas.com:389/saschannelcn=Product Announcements, cn=saschannels,sascomponent=sasPublishSubscribe,cn=SAS,o=SAS Institute,c=US/saschannel.7 Found Channel+ldap://d5296.us.sas.com:389/saschannelcn=Human Resources, cn=saschannels,sascomponent=sasPublishSubscribe,cn=SAS,o=SAS Institute,c=US/saschannel.8 Found Channel+omi://d5296.us.sas.com:4059/A5CK95WU.AB000001/ITChannel.18 Found Channel+omi://d5296.us.sas.com:4059/A5CK95WU.AB000002/ITChannel.19 Found Channel+omi://d5296.us.sas.com:4059/A5CK95WU.AB000003/ITChannel.20 Found Channel+omi://d5296.us.sas.com:4059/A5CK95WU.AB000004/ITChannel.21 |
MetadataInterface mi = _informationService.fetchEntityByKey( user, "Channel+ldap://d5296.us.sas.com:389/saschannelcn=Human Resources," + "cn=saschannels,sascomponent=sasPublishSubscribe,cn=SAS,o=SAS Institute," + "c=US/saschannel" ); if ( mi != null ) { System.out.println( "Got channel " + mi.getName() ); } |
While the Information Service is useful as it is, its real power comes from its extensibility. There are a basic set of classes and services that may be used to access information and manipulate it, but it may not provide the most useful functional interface for all types of data.
Metadata objects or "smart objects" are wrapper objects that hide the underlying data model, and specifics of different repository types. They are intended to provide data access and update capabilities. They are not intended to provide mechanisms to perform functions against physical resources that the metadata may represent. For instance, the StoredProcess smart object provides access to the stored process metadata. It does not, however, provide methods to execute the stored process and get results. The smart object can be used to instantiate an object that has these capabilites, though.
The first thing to consider when creating a new smart object is that you want to create an interface that's functionally useful, and not necessarily directly reflective of the underlying model. Provide methods that access data that's useful to a client, even if it involves getting data from other objects.
The first task is to create an interface for your new type that
extends com.sas.services.information.metadata.MetadataInterface
.
This is the base class that all the Information Service repositories
work with. As a SAS standard, the interface name should end with
"Interface", so if you're creating a "Widget" smart object, your
interface would be "WidgetInterface". Since MetadataInterface is
a remote interface, all public methods have to throw
java.rmi.RemoteException. To be consistent with MetadataInterface,
they should also throw com.sas.services.ServiceException.
Once the interface is done, you can start on your implementation. All
smart objects inherit from the
com.sas.services.information.metadata.Metadata
class. The
implementation must deal with the underlying, repository-specific
objects. For objects in OMR, that means jOMA classes. Other
repository types have sets of classes for representing the data that
comes from them. Hiding these details is what the smart objects are
for. There are two models that you can use for your implementation.
If your objects are only contained in one repository type, like OMR,
you probably only need one implementation. If your objects can come
from more than one type of repository, like OMR and LDAP, you can have
one implementation that can work with both types of repositories, or
you can have different implementations. One of the advantages of
having different implementations is that they can have different
inheritance. Since any object in LDAP can have children, it can
be useful for LDAP implementations to inherit from Folder, while
the OMR implementation inherits from Metadata (since only a Tree
can have children in OMR). The preferred implementation is to have
one per repository type, for example, an OMRWidget that implements
the WidgetInterface for the OMR repository, and an LDAPWidget for
the LDAP repository.
Your first order of business, of course, is constructors. For OMR objects, you'll need five constructors. One is the default constructor that takes no arguments. Then one each that takes as an argument the jOMA object's interface, implementation, and stub. So, if the Widget is based on a Transformation object, you'd have constructors of:
public OMRWidget() throws RemoteException { super(OMRWidget.class); } public OMRWidget(com.sas.metadata.remote.Transformation o) throws RemoteException { super(o, OMRWidget.class); } public OMRWidget(com.sas.metadata.remote.impl.TransformationImpl o) throws RemoteException { super(o, OMRWidget.class); } public OMRWidget(com.sas.metadata.remote.impl.Transformation_Stub o) throws RemoteException { super(o, OMRWidget.class); }
You'll notice that each constructor calls a super constructor that takes the class as an argument. That's because the class is used to determine RMI socket factories. If you're going to allow your class to be extended, you'll also need to add those constructors as protected methods.
protected OMRWidget(Class c) throws RemoteException { super(c); } protected OMRWidget(com.sas.metadata.remote.Transformation o, Class c) throws RemoteException { super(o, c); } protected OMRWidget(com.sas.metadata.remote.impl.TransformationImpl o, Class c) throws RemoteException { super(o, c); } public OMRWidget(com.sas.metadata.remote.impl.Transformation_Stub o, Class c) throws RemoteException { super(o, c); }
You'll need one more constructor that creates a new object that takes the Repsitory, a name, and a parent folder as arguments:
public OMRWidget( RepositoryInterface repos, String name, FolderInterface parent ) throws RemoteException { super(OMRWidget.class); if ( (repos == null) || (name == null)) throw new IllegalArgumentException(); try { OMIRepositoryInterface omirepository = (OMIRepositoryInterface)repos; Transformation transform = (Transformation) omirepository.createMetadata(name, MetadataObjects.TRANSFORMATION); // Why this is important will be clear later. transform.setTransformRole = "Widget". _repositoryObject = transform; _repositoryFlag = Metadata.REPOSITORY_OMR; setRepository( repos ); if ( parent != null ) { try { parent.addItem( this ); } catch( Exception ex ) {} } } catch(Throwable t) { logMessage(LoggerInterface.WARN, RB.getStringResource( "Metadata.accessexcept.msg.txt"), "com.sas.services.information.metadata.OMRDataTable", t); } }
Especially note the construct:
try { } catch(Exception ex) { logMessage(LoggerInterface.WARN,...); }
This is the standard mechanism for logging exceptions from the metadata classes. If the method signature throws ServiceException, rather than logging the message, you should;
try { } catch(Exception ex) { throw new ServiceException(ex, ex.getMessage()); }
There are some details you need to keep in mind when implmenting a Metadata subclass. There will most likely be instances where the data you will be returning will be another instance of a Metadata subclass that you created. For instance, the com.sas.services.information.metadata.ChannelInterface will return a DirectoryInterface object for the getDefaultArchivePath method. The correct way to create this is by using the com.sas.services.information.Factory object:
MetadataInterface mi = _repository.factoryProcess( object );
Where object is the object that came from the repository. Older smart object classes created their own factory, but using the repository's factory allows it to better control the smart object cache and do any necessary processing of the smart object (like calling setRepository).
The Metadata class has several methods for handling logging:
public void logMessage( int level, String message, String context, Throwable t) throws RemoteException public void logThrowable( int level, String message, String context, Throwable t) throws RemoteException public void logFormat(int level, String context, ResourceBundle bundle, String key, Object arg0) throws RemoteException public void logFormat(int level, String context, ResourceBundle bundle, String key, Object arg0, Object arg1) throws RemoteException public void logFormat(int level, String context, ResourceBundle bundle, String key, Object arg0, Object arg1, Object arg2) throws RemoteException public void logFormat(int level, String context, ResourceBundle bundle, String key, Object arg0, Object arg1, Object arg2, Object arg3) throws RemoteException public void logMessage(int level, String message) throws RemoteException
In each of these methods, level is one of the levels defined in
com.sas.services.logging.LoggerInterface (DEBUG, INFO, WARN, ERROR, FATAL).
The context should be the class name of the calling class. In general,
logging above the DEBUG level should be kept to a minimum. If an error
condition occurs, an exception (preferably a ServiceException) should be
thrown.
Now that you have an interface and an implementation that represents the data, you need to have that class instantiated by the repositories when that type is returned by a search or a fetch. To accomplish this, you have to update the Factory section of the Information Service configuration. The configuration will include the protocol(s) of the repositories that your type might be in (omi, ldap, dav), the repository-specific type (e.g., Transformation), an optional filter so that your type can be distinguished from others of the same type, but possibly different semantics, and the full-qualified Java class name to instantiate.
In order to update the configuration, use the Foundation Services Manager plug-in to the SMC. Open the "Foundation Service Manager", then the deployment and group you wish to update. Select "Information Service" (the exact name may be different, but it should include 'Information' in it somewhere, then right-click on it to bring up the options menu. Select "Properties". This will bring up the Information Services configuration page. Select the "Service Configuration" tab, then press the "Edit Configuration" button. Select the "Factories" tab, then select the appropriate protocol in the combo box (omi for objects in the metadata server). If your type can be contained in multilpe repository types, you will have to enter a configuration for each protocol. Check to see if the metadata type is already listed in the "Types" list. If so, select it and press the "Edit" button to add your new class to this type. Otherwise, press the "New" button to bring up the dialog to create a new type definition.
If this is a new entry, enter the metadata type (e.g., Transformation) in the Type field. Then click the "Add..." button to bring up the dialog to create a new factory action.
You'll want to create a "constructor" action type. For now, don't worry about the other two types. Enter the fully-qualified (including the package) name of your new Metadata subclass. If the type you're using is a common, generic type (like Transformation or DeployedComponent), you'll probably need to enter a filter. The filter has the format @attribute='value'. Where attribute is the name of an attribute of the metadata object. In our example, a Tranformation is a rather generic type, and both Information Maps and Reports use that as a base type so we'll need to distinguish our new Widget type from them. We'll use a TransformRole of "Widget" to trigger creation of our class, so the filter would be "@TranformRole='Widget'".
Once your configuration is in place, make sure your class is in a jar file or directory that's searched by your application's class loader, and then you're ready to try it out. If you perform an Information Service operation that returns an object of that type, you should get an instance of your new class back.
Now that you have a class that represents your data, you'll probably want a way to easily find that kind of data. There is a generic filtering capability in the Information Service, but by creating a custom filter for your type, you can provide more intuitive and powerful searching capbilities.
It's difficult to create a generic filter that hides model
differences. Different repository types will generally use different
type names, attribute name, even different query languages. However, a
type-specific filter class can easily accomodate these differences and
hide them from the client application. The first thing to consider is
what attributes are likely to be useful to search on. If you want to
search on keywords, you'll want to subclass the
com.sas.services.information.metadata.KeywordFilter
class. This will give you the keyword add/remove functionality. It
is still up to your filter class to generate the correct search syntax
for your repository(s), though. Each repository type will make a
different call to get a search string that it can use. OMI
repositories will call getXMLSelectString()
, and LDAP
repositories will call getLdapString()
. Your filter
class has to do the work of creating those strings based on the set
search criteria and returning it to the repository. Besides an
XMLSelect string, your filter can also set a template that the
Repository will use to retrieve your data. Templates can do some
filtering as well as control the data that's returned, so it may be
necessary to set the template depending on what search criteria was
set.
|
Foundation |
|
| |||||||||||
PREV PACKAGE NEXT PACKAGE | FRAMES NO FRAMES |