Method and system for uniformly accessing multiple directory services5893107Abstract The present invention provides a directory service system for accessing a plurality of directory services in a uniform manner. Each directory service manages information relating to objects or that directory service. The type of information that a directory service manages for an object is defined by the object class of the object. An object class defines the properties (i.e., information) that a directory service manages for objects of that object class. Each property has a properly name and property type. A directory service has a property value for each property defined by the object class of each object. The directory service system comprises a schema browsing component, a name resolving component, a binding component, and an extending component. The schema browsing component controls the retrieving of the property name and property type of each property of each object class of each directory service. A client of the directory service system uses the schema browsing component to retrieve property names and property types of the object classes. The name resolving component controls the receiving of a unique identifier of an object within a directory service and the locating of the object within the directory service. The binding component controls the binding to an in-memory object representing a located object within a directory service. The extending component controls the defining of new object classes and new properties for each directory service. A client of the directory service system uses the extending component to define new object classes and new properties. Claims We claim: Description TECHNICAL FIELD
______________________________________
@NamespaceIdentifier|<namespace path to object>
or
NamespaceIdentifier://<namespace path to object>
______________________________________
The "NamespaceIdentifier" uniquely identifies the directory service. The string after the "|" or ":" is in a directory service-dependent format. That is, each directory service can define its own format. OleDs provides a binding function for binding an OleDs path to an OleDs object that corresponds to the object identified by the OleDs path. This binding function is passed an OleDs path and returns a pointer to the OleDs object corresponding to the object. To bind to the OleDs object, the binding function parses the namespace identifier from the OleDs path. The binding function uses this namespace identifier to retrieve code (e.g., in a dynamic linked library) that implements access to the identified namespace. (OleDs maintains a registry for directory service providers to register the location of their implementation of OleDs interfaces.) The binding function then invokes the retrieved code passing the OleDs path. The invoked code uses the API set of the directory service to access the identified object and implement the behavior defined by the OleDs interface. OleDs models the objects of each directory service as either OleDs container objects and OleDs leaf objects. To access a container object of a directory service, an OleDs container object is instantiated that has an implementation provided by the provider of the directory service. Similarly, to access of leaf object of the directory service, an OleDs leaf object is instantiated that has an implementation provided by the provider of the directory service. Each OleDs object exposes an interface for accessing the properties of the corresponding object of the directory service. Each OleDs container object also exposes an interface through which its contained objects can be accessed by a client. FIG. 4 illustrates the relationship between OleDs objects and their corresponding objects in a directory service. Each directory service provider provides implementations of each interface exposed by the OleDs objects. These implementations effect a mapping of the behavior of the provider's API set to the behavior of the OleDs interfaces. Each client can thus uniformly access multiple directory service using the providers' implementations of the OleDs interfaces. To assist clients in accessing the objects of a directory service, OleDs defines a model for representing the schema of each directory service. Conceptually, each directory service is viewed as having a schema container object that contains a schema object for each object class. The schema object defines the properties of the object class. The schema container is assigned a pre-defined name within the namespace, such as "schema." To access a schema container, a client uses the OleDs binding function passing the OleDs path of the schema (e.g., "@WinNTDS|Schema"). The OleDs binding function returns a pointer to an OleDs container object corresponding to the schema container object. A client can use to interfaces of the OleDs container object to enumerate the contained schema objects. In this way, a client can determine the definition of the various object classes at run time. In addition, a client can define additional object classes by adding schema objects to the schema container object. OleDs pre-defines several object classes to facilitate the implementation of the mapping of the behavior of a directory service API set to the behavior of the OleDs interfaces. In particular, OleDs provides object class definitions for common container objects, such as organizational unit and country, and for common leaf objects, such a user or a resource. OleDs defines the concept of a default namespace or directory service. A default namespace is that namespace that a client wishes to use when a namespace is not specified in the OleDs path. The identification of the default namespace for a client or user would be typically stored in the registry indexed by the client or user. OleDs also defines the concept of a default OleDs object that is implemented by each provider to allow a client to add objects of a newly defined object class to the directory service. A directory service provider would typically provide an implementation of an OleDs object for each object class within the directory service. For example, a provider may provide different implementations for the object classes for a computer object and company object. Each implementation is aware of which properties are defined for the corresponding object class and can request the API set to retrieve the values of the properties. However, if a client defines a new object class at run time, there will be no corresponding implementation. Consequently, each provider that allows new object classes to be defined for its directory service, also provides an implementation of a default OleDs object. Whenever a client requests to instantiate an OleDs object for an object for which there is no implementation, a default OleDs object is instantiated. The default OleDs object contains the name of the object class to which it corresponds, which it uses to retrieve the definition of the object class from the schema. Using this definition, the default OleDs object can access the corresponding object of the directory service. FIG. 3A is a block diagram illustrating the hierarchy of OleDs. The hierarchy includes a root container referred to as "Namespaces." The Namespaces container logically contains the namespace of each directory service. FIG. 3B is a block diagram illustrating the architecture of OleDs. Each provider of a directory service implements a mapping the behavior of provider's API set to the behavior of the OleDs interfaces. Each OleDs container object corresponds to a container object in the directory service, and each OleDs leaf object corresponds to a leaf object in the directory service. OleDs provides for both early and late binding of a client to the OleDs objects. Early binding means that the developer of a client knows at compile time the name of the properties of each object. Thus, the client can be programmed to access the values from these properties directly using methods for "getting" and "putting" properties (e.g., "get.sub.-- Address" or "put.sub.-- Name"). Each OleDs object exposes the IDispatch interface (defined in Ole 2.01), which allows a client to bind to property at run time (i.e., late binding). Each OleDs object exposes the IOleDs interface. The IOleDs interface defines various attributes of the OleDs object along with methods for accessing the properties of the corresponding object in the directory service. (In the following, the term "property" refers to data within a directory service, and the term "attribute" refers to data defined by OleDs for the OleDs objects.) Each OleDs object that is a container also provides the IOleDsContainer interface. The IOleDsContainer interface defines various attributes related to container objects and methods for manipulated contained objects. In addition, each OleDs object may optionally provide an interface named IOleDs<class> or IOleDs<class>operations, where <class> corresponds to the name of the object class of the corresponding object in the directory service and <class> operations corresponds to the operations that can be performed on an object. For example, a point queue object has operations such as pausing, starting, and deleting a print job. In the following, each of these and other OleDs interfaces are defined, along with a description of a sample implementation of certain methods of the interfaces. FIG. 4 is a block diagram of a computer system configured to implement the present invention. The computer system 400 includes a memory 401, a central processing unit 402, and I/O Interface 403, storage devices 404, and a display device 405. Various implementations of the OleDs objects are stored on a computer-readable memory, such as a disk. These implementations are loaded into the computer memory for execution. The computer memory contains clients 406, 407. The memory also contains an implementation of the OleDs objects 408, 411, 414 for three directory services. Each implementation of the OleDs objects accesses the corresponding API set of the directory services 409, 412, 415. These API sets provide access to the properties of the objects 410, 413, 416 for the underlying directory service. In addition, OleDs itself provides implementations of the binding function, the namespaces container, and various OleDs objects that correspond to typical objects of directory services. OleDsGetObject Function FIG. 5 is a flow diagram of the function OleDsGetObject, which is the binding function of OleDs. The function OleDsGetObject is passed an OleDs path to an object and an interface identifier, and returns a pointer to the identified interface for an OleDs object that represents the object. In step 501, the function parses the namespace identifier from the OleDs path. The function uses the namespace identifier to locate the implementation of a parsing interface provided by the provider of the identified namespace. In step 502, the function calls function CoGetClassObject (defined by OLE 2.01) to retrieve a pointer to a class factory object for a ParseDisplayName class implemented by the provider of the identified namespace. The function dynamically generates the class identifier of the ParseDisplayName class for the identified namespace. For example, if the parsed namespace is "WinNTDS," then the class identifier may be "CLSID.sub.-- PDNWinNTDS." The class factory object instantiates an object of the ParsedDisplayName class, which exposes the IParseDisplayName interface to be instantiated. The IParseDisplayName interface provides a method for returning a moniker to the OleDs object for the object identified by the OleDs path. In step 503, the function invokes the method IClassFactory::CreateInstance passing the identifier of the IParseDisplayName interface and receives a pointer to the IParseDisplayName interface. The method CreateInstance creates an instance of the class identified by the dynamically generated class identifier. In step 504, the function invokes the method IParseDisplayName:: ParseDisplayName passing the OleDs path. The method ParseDisplayName returns a pointer to a moniker (IMoniker interface) for the object identified by the OleDs path. In step 505, the function invokes the method IMoniker::BindToObject to retrieve a pointer to the OleDs object representing the object identified by the OleDs path. The function then returns the pointer to the OleDs object. IOleDs Interface The IOleDs interface defines attributes and methods common to each OleDs object. The following is the interface definition along with a description of its attributes and methods:
__________________________________________________________________________
Interface IOleDs:IDispatch
{ HRESULT get.sub.-- Name (string *pName)
HRESULT get.sub.-- Class (string *pClass);
HRESULT get.sub.-- GUID (string *pGUID);
HRESULT get.sub.-- OleDsPath (string *pOleDsPath):
HRESULT get.sub.-- Parent (string *pParent Container);
HRESULT het.sub.-- Schema (string *pSchemaClassObject);
HRESULT Access (IOleDsAccess **ppComponentAccessControl);
HRESULT PropAccess (string PropName, IOleDsAccess **ppComponentAccess
Control);
HRESULT GetInfo (Variant vHints);
HRESULT SetInfo (void);
HRESULT Get (string Name, variant *pProp);
HRESULT Put (string Name, variant Prop);
__________________________________________________________________________
Name Description
__________________________________________________________________________
Attributes
Name Relative name of this object within tile container object.
OleDsPath
OleDs path of this object.
Class Name of the object class of this object.
GUID Unique identifier for objects of this object class.
Parent
OleDs path of the container object of this object.
Schema
OleDs path of the schema object that represents this object class.
Methods
Access
Obtains the IOleDsAccess interface (described below) of the OleDs
access control object that
corresponds to the security permissions on this object.
PropAccess
Returns a pointer to the IOleDsAccess interface of the OleDs access
control dependent object
that represents the access control on a property of this.
GetInfo
Retrieves the property values of this object from the directory
services and stores them in the
OleDs object. The vHints parameter allows a client to indicate
which functional sets or
properties should be retrieved so the method can optimize network
access.
SetInfo
Commits changes to this object. If properties have been changed on
this object, the method
writes the property values from the OleDs object to the directory
service.
Get Retrieves the value for a named property from the OleDs object.
Put Sets the value for a named property in the OleDs object.
__________________________________________________________________________
FIGS. 6 and 7 illustrate implementations of methods of the IOleDs interface. FIG. 6 is a flow diagram of the method IOleDs::GetInfo. This method loads the properties for the object from the directory service into the OleDs object. OleDs defines a committing or transactioning process by which updates to properties of object are stored only in the OleDs object (using the method Put) and only stored in the directory service when a client requests (using the method Set Info). When an OleDs object is first instantiated, for example, when the function OleDsGetObject is invoked, the instantiated object would typically contain the handle that identifies the object to the API set of the directory service. In step 601, the method invokes the function ReadObject passing a handle to the object and a list of property names for the object. The function ReadObject retrieves the values for the named properties from the directory service. In an implementation of the method GetInfo for an OleDs default object, the method retrieves a list of the property names from the schema object for the object class of this object. In steps 702-704, the method loops storing the retrieved values in the OleDs object. In step 702, the method selects the next value starting with the first. In step 703, if all the values have already been selected, then the method returns, else the method continues at step 704. In step 704, the method stores the selected value into the OleDs object and loops to step 702 to select the next value. FIG. 7 is a flow diagram of the method IOleDs::Get. The method is passed a property name, retrieves its value of the passed properly from the OleDs object, and returns. IOleDsContainer Interface The IOleDsContainer interface defines attributes and methods common to each OleDs object that is a container. The following is the interface definition along with a description of its attributes and methods.
__________________________________________________________________________
Interface IOleDsContainer:IDispatch
HRESULT get.sub.-- Count (long *pCount);
HRESULT get.sub.-- NewEnum (IUnknown *ppEnum);
HRESULT get.sub.-- Filter (Variant *pFilter);
HRESULT put.sub.-- Filter (Variant Filter);
HRESULT GetObject (string Class, String RelativeName, IOleDs
**ppNamedObject);
HRESULT Create (string Class, string RelativeName IOleDs
**ppNewObject);
HRESULT Delete (string Class, string RelativeName);
HRESULT CopyHere (string SourceObject, string NewName, IOleDs
**ppNewObject);
HRESULT MoveHere (string SourceObject, string NewName, IOleDs
**ppNewObject);
}
__________________________________________________________________________
Name Description
__________________________________________________________________________
Attributes
Filter
Array of filters for the object classes that will be returned in a
given enumeration. If Filter is
empty, then all objects of all object classes are returned. Each
array entry has the following
format.
<FilterEntry>::=<ClassName>
.vertline.<ClassName><PropName>
.vertline.<ClassName><FuncSetName>
.vertline.<ClassName><FuncSetName><PropName>
.vertline.<GUID>
.vertline."Provider Specific String"
Count Number of OleDs objects within the container that pass the filter.
.sub.-- NewEnum
Enumerator of contained objects.
Methods
GetObject
Returns the IOleDs interface of the object in this container object
identified by the object
class and relative name within the container object. If the object
class is not passed, the
method returns the interface for the first object found with that
relative name.
Create
Creates an object of the specified object class and relative name
within this container object
and returns a pointer to the IOleDs interface. The object is not
actually created within the
directory service until the method IOleDs::SetInfo is invoked so
that the mandatory
properties can be set.
Delete
Deletes the object identified by the object class and relative name
within this container object.
CopyHere
Creates a new object in this container object that is identical to
the specified object and
returns a pointer to the IOleDs interface to the new object.
NewName is an optional
parameter that, if present, contains the name of the new object
within the container object.
MoveHere
Same as method CopyHere, except that the source object is deleted
after copying.
__________________________________________________________________________
FIG. 8 is a flow diagram of the method IOleDsContainer:: CopyHere. This method creates a copy of the specified source object within this container object. In step 801, the method determines the object class of the object to be copied. In step, 802, the method creates an object in the directory service of the determined object class by invoking the function CreateObject. In step 803, the method retrieves the OleDs path to the schema object for the object class. In step 804, the method invokes the function OleDsGetObject passing the OleDs path to the schema object, and retrieves a pointer to the IOleDs interface of OleDs schema object. In step 805, the method invokes the method lOleDs::QueryInterface of the OleDs schema object to retrieve a pointer to the IOleDsClass interface. The IOleDsClass interface provides methods for retrieving the name of the properties defined for this object class. In step 806, the method invokes the method IOleDsClass::GetMandatoryProperties to retrieve a list of the mandatory properties of this schema class. In step 807, the method invokes the method IOleDs::Properties to retrieve any additional properties for this class. In steps 808-811, the method loops copying each of the values of the properties from the source OleDs object to the destination OleDs object. In step 808, the method selects the next property startling with the first. In step 809, if all the properties have already been selected, then the method continues at step 812, else the method continues at step 810. In step 810, tie method invokes the method IOleDs::Get of the source OleDs object to retrieve the value for the selected property. In step 811, the method stores the retrieved value in the destination IOleDs object and loops to step 808 to select the next property. In step 812, the method copies the attributes of the source OleDs object to the destination OleDs object, and returns. FIG. 9 is a flow diagram of the method IOleDs::Create. In step 901, the method invokes the function CreateObject of the directory service passing an object class and receives a handle to a newly created object of that object class. IOleDsClass Interface The IOleDsClass interface is exposed by an OleDs schema object to provide access to the definition of the object class. The following is the interface definition along with a description of its attributes and methods.
__________________________________________________________________________
Interface IOleDsClass: IOleDs
HRESULT get.sub.-- PrimaryInterface (string *GUID);
HRESULT get.sub.-- CLSIC (string CLSID);
HRESULT put.sub.-- CLSID (string CLSID);
HRESULT get.sub.-- OID (string *OID);
HRESULT put.sub.-- OID (string OID);
HRESULT get.sub.-- Abstract (boolean *Abstract);
HRESULT put.sub.-- Abstract (boolean Abstract);
HRESULT get.sub.-- MandatoryProperties (Variant *Mandatory);
HRESULT put.sub.-- MandatoryProperties (Variant Mandatory);
HRESULT get.sub.-- DerivedFrom (Variant *pDerivedFrom);
HRESULT put.sub.-- DerivedFrom (Variant DerivedFrom);
HRESULT get.sub.-- Containment (Variant *pContainment);
HRESULT put.sub.-- Containment (Variant Containment);
HRESULT get.sub.-- Container (boolean *pContainer);
HRESULT put.sub.-- Container (boolean Container);
HRESULT get.sub.-- HelpfileName (string *pHelpFile);
HRESULT put.sub.-- HelpFileName (string HelpFile);
HRESULT get.sub.-- HelpFileContext (long *pHelpContext);
HRESULT put.sub.-- HelpFileContext (long HelpContext);
HRESULT Attributes (IOleDsCollection **ppAttributes)
}
__________________________________________________________________________
Name Description
__________________________________________________________________________
Attributes
CLSID CLSID of the code implementing the OLE DS object for this
object class.
OID Namespace-specific object identifier defining this object
class. This is provided to
allow schema extension via OLE DS in namespaces that require
namespace-specific
OIDs for object classes.
Abstract Boolean value indicating whether this object class is
abstract.
MandatoryProperties
List of the properties that must be set for this object class
to be written to storage.
Primary Interface
The primary interface identifier for objects of this object
class. This is the IID for
the interface defining the class, for example, the "user" class
if defined by
supporting IOleDsUser.
DerivedFrom
Array of OleDs path strings that indicate the immediate
superclasses from which
this object class was derived.
Container Property that determines if this object class is a container.
HelpFileName
Name of a help file that contains further information about
objects of this object
class.
HelpFileContext
Context ID inside HelpFileName where specific information on
this object class can
be found.
Method
Attributes
Returns a collection of OleDs objects describing additional
attributes›delete???! of
this property. Attribute objects are provider-specific›???!
__________________________________________________________________________
IOleDsProperty Interface OleDs defines an OleDs property object that corresponds to each property of an object class. An OleDs property object exposes an interface for retrieving the definition of the property. The following is the definition of the interface along with a description of the attributes and methods.
______________________________________
interface IOleDsProperty: IOleDs
HRESULT get.sub.-- OleDsNames (Variant *pOleDsNames);
HRESULT get.sub.-- DsNames (Variant *pDsName);
HRESULT get.sub.-- OID (string OID);
HRESULT put.sub.-- OID (string *OID);
HRESULT get.sub.-- Syntax (string *pSyntax);
HRESULT put.sub.-- Syntax (string Syntax);
HRESULT get.sub.-- MaxRange (long *pMaxRange);
HRESULT put.sub.-- MaxRange (long MaxRange);
HRESULT get.sub.-- MinRange (long*pMinRange);
HRESULT put.sub.-- MinRange (long MinRange);
HRESULT get.sub.-- MultiValued (long *pMultiValued);
HRESULT put.sub.-- MultiValued (long MultiValued);
HRESULT Attributes (IOleDsCollection **pAttributes)
};
______________________________________
Name Description
______________________________________
Attributes
OleDsNames
Array of strings containing the names by which OleDs
can access this property.
DsNames Array of strings containing the names by which the
underlying namespace can access this property.
OID The namespace-specific object identifier defining this
property.
Syntax Relative path of the schema syntax object defining the
syntax of this property.
Relative to the current schema container.
MaxRange
Upper limit of values assigned to the property.
MinRange
Lower limit of values assigned to the property.
Normal Value that determines if this property should be replicated
normally.
MultiValued
Value that determines if this property is multi-valued.
Method
Attributes
Returns a collection of OLE DS objects describing
additional attributes of this property.
______________________________________
IOleDs <Class> Interface OleDs defines that every object class can support an interface named IOleDs<class>, where <class> is the name of the object class. This interface has a method, each functional set supported by the object class, that returns a pointer to an OleDsFunctionalSet object for that functional set. The following is a description of a sample functional set interface.
______________________________________
Interface IOleDsUser: IOleDs
HRESULT get.sub.-- BusinessInfo
(IOleDs FS UserBusinessInfo **ppFuncSet);
};
______________________________________
OleDs Object Class Definitions OleDs defines various object classes for both container and leaf objects. The following object classes are defined for container objects by OleDs: Namespaces Namespace Country Locality Organization Domain Organizational Unit Computer File Service The following object classes are defined for leaf objects by OleDs: User Group Alias Service Print Queue Print Job Print Device Session Resource File Share The namespaces object is a container of the namespace objects. The implementation of the namespaces object is part of OleDs. The namespaces object exposes the IOleDs namespaces interface. This interface is shown below.
______________________________________
InterfaceIOleDsNamespaces: IOleDs
get.sub.-- DefaultContainer (string * pDefault);
set.sub.-- DefaultContainer (string Default);
}
______________________________________
The namespace object is a container object that is the source of all OleDs objects for a given namespace. The country object is a container object with properties that relate to a country. The locality object is a container object with properties that relate to a locality (e.g. state). The following is a definition of an interface for getting and setting the properties of a locality object.
__________________________________________________________________________
Interface IOleDsFSLocalityGeneralInfo: IDispatch
HRESULT get.sub.-- Description (string *Description);
HRESULT put.sub.-- Description (string Description);
HRESULT get.sub.-- LocalityName (string LocalityName);
HRESULT put.sub.-- LocalityName (string LocalityName);
HRESULT get.sub.-- PostalAddress (string *pPostalAddress);
HRESULT put.sub.-- PostalAddress (string PostalAddress);
HRESULT get.sub.-- SeeAlso (VARIANT *pSeeAlso);
HRESULT put.sub.-- SeeAlso (VARIANT Also);
HRESULT Access (IOleDsAccess **ppFuncSetAccessControl);
HRESULT PropAccess (string PropName, IOleDsAccess **ppPropAccessControl);
};
Name Description
__________________________________________________________________________
Method
Access Returns a pointer to the IOleDsAccess interface on the Access
Control dependent object
that represents the access control on this interface.
PropAccess
Returns a pointer to the IOleDsAccess interface on the Access
Control dependent object
that represents the access control on a property on this
interface, as indicated by
PropName.
Attributes
Description
Text that describes the Locality.
LocalityName
Locality name. The locality name identifies a geographical area in
which the container
is physically located.
Postal Address
Main postal address of the locality.
SeeAlso
Array of names of other directory objects which may be relevant to
this object.
__________________________________________________________________________
Each of the OleDs-defined object classes define properties related to objects of that object class. The domain object corresponds to a domain in a computer system and has functional set with properties (e.g., passwords) relating to the domain object. The organizational unit object corresponds to an entity, such as a company. The computer object corresponds to a computer in a domain. The user object has a business information, account restrictions, account statistics, and other information functional set to describe a user. The business information functional set contains properties relating to the description of a business such as the country, division, department, manager, office locations. The account restrictions functional set includes properties describing the general characteristics of the user's account, such as account disabled, number of hours in which the user is allowed to log in, etc. The account statistics functional set has property specifying statistics of the user's account, such as last time the password was changed and the last time the user logged into the account. The other info functional set contains properties relating to miscellaneous user information such as email address, home directory. Example Client of OleDs FIG. 10 is a flow diagram of the procedure to display all namespaces. This procedure illustrates how a client of OleDs would retrieve the names of all the namespaces. In step 1001, the procedure invokes the function CoGetClassObject passing the class for the OleDs namespaces object and the interface identifier for the IClassFactory interface. The function returns a pointer to a class factory interface for the OleDs namespaces object. In step 1002, the procedure invokes the method IClassFactory::Createlnstance, passing the interface identifier for the IOleDsContainer interface. The method creates an instance of the OleDs namespaces object and returns a pointer to the IOleDsContainer interface. In step 1003, the procedure invokes the method IOleDsContainer::get.sub.-- NewEnum to retrieve an enumerator for the namespaces object. In step 1004-1008, the procedure loops enumerating each namespace and displays the namespace. In step 1004, the procedure invokes the method IEnumerator::Next to retrieve a pointer to the lUnknown interface for a namespace object. In step 1005, if all the namespaces have already been enumerated, then the procedure returns, else the procedure continues at step 1006. In step 1006, the procedure invokes the method lUnknown::QueryInterface to retrieve a pointer to the IOleDs interface for the namespace object. In step 1007, the procedure invokes the method IOleDs::get.sub.-- Name to retrieve the name associated with the namespace object. In step 1008, the procedure displays the retrieved name and then loops to step 1004 to enumerate the next namespace object. Although the present invention has been described in terms of a preferred embodiment, it is not intended that the invention be limited to this embodiment. Modifications within the spirit of the invention will be apparent to those skilled in the art. The scope of the present invention is defined in the claims that follow.
|
Same subclass Same class Consider this |
||||||||||
