Title of Invention

"SYSTEM DATA INTERFACES, RELATED ARCHITECTURES, PRINT SYSTEM DATA INTERFACES AND RELATED PRINT SYSTEM ARCHITECTURES"

Abstract A system data interface and related architectures are described, Various embodiments can provide one or more of the following capabilities: a generic data model, asynchronous client and server dispatch, cancellation, batching, transactional invocation, parallel invocation, interception or reflection. In one embodiment, the sys'tem data interface is employed in the context of a print system.
Full Text TECHNICAL FIELD
This invention pertains to system data interfaces and related architectures
and, in particular embodiments, to print system data interfaces and related
architectures.
BACKGROUND
Some systems can include servers that process data and communicate with
various types of clients. One particular type of system is a print system which can
include print servers that provide access to job, device, logical server and forms
data from various clients. Many current systems, such as print systems, have an
inflexible underlying network interface that must be modified whenever new
classes of data need to be supported. In addition, some interfaces require much
more communication than may be necessary and can create context on the server,
both of which can limit server performance in whatever context in which the
server is employed, such as print systems and others.
Other problems that plague many systems can lead to inflexibility,
difficulty in extensibility and limitations insofar as being protocol specific.
Accordingly, this invention arose out of concerns associated with providing
improved systems, including improved data interfaces and related architectures.
SUMMARY
A system data interface and related architectures are described. Various
embodiments can provide one or more of the following capabilities: a generic data
model, asynchronous client and server dispatch, cancellation, batching,
transactional invocation, parallel invocation, interception or reflection.
In one embodiment, the system data interface is employed in the context of
a print system.
BRIEF DESCRIPTION OF THE DRAWINGS
Fig. 1 is a high level block diagram that illustrates various components of a
data interface in accordance with one embodiment.
Fig. 2 is a high level block diagram of a printing data access interface in
accordance with one embodiment
Fig. 3 is a block diagram that illustrates aspects of out of process
communication in accordance with one embodiment.
Fig. 4 is a high level block diagram that illustrates an overview of a security
model in accordance with one embodiment.
Fig. 5 is a block diagram that illustrates a type.mapping mechanism in
accordance with one embodiment.
Fig. 6 is a block diagram of common data interface object access interface
in accordance with one embodiment.
Fig. 7 is a block diagram of a dynamic type map in accordance with one
embodiment.
Fig. 8 is a block diagram that illustrates object creation and cloning in
accordance with one embodiment.
Fig. 9 is a block diagram that illustrates an abstract hierarchy in accordance
with one embodiment.
Fig. 10 is a block diagram that illustrates a class hierarchy and security in
accordance with one embodiment.
Fig. 11 is a block diagram that illustrates metadata that is retrieved from an
object in accordance with one embodiment.
Fig. 12 is a block diagram of exemplary components of a common data
interface in accordance with one embodiment.
Fig. 13 is a block diagram of a plug-in model in accordance with one
embodiment.
Fig. 14 is a block diagram of an object model that is used by a collection
adapter service in accordance with one embodiment.
Fig. 15 is a block diagram that illustrates framework-provided collections
in accordance with one embodiment.
Fig. 16 is block diagram that illustrates interoperability of managed and
unmanaged objects in accordance with one embodiment.
Fig. 17 is a block diagram that illustrates exemplary components of a
computing device that can be utilized to implement one or more embodiments.
DETAILED DESCRIPTION
Overview
In the discussion that follows, a novel system is provided that addresses
many of the shortcomings of current systems. In one embodiment, the system, its
data interfaces and related architectures are described in the context of a print
system. It is to be appreciated and understood, however, that the inventive
features described below can be employed in systems other than print systems,
without departing from the spirit and scope of the claimed subject matter.
While the described print system does many things, fundamentally the
described print system accomplishes a couple of primary tasks. First, the print
system manages data including a set of objects and their properties. Second, the
print system schedules print jobs first for rendering, and then sends the print jobs
to the appropriate print device for printing. The process of rendering a document
can be accomplished by retrieving configuration information from a data interface
which specifies which components to invoke to process the job. Thus, the way the
print system represents data can be fundamental to its operation.
To accomplish these primary tasks, the inventive print system utilizes a
specific interface and related components. This interface and print systems of
which it is a part can provide a number of advantages over current print systems.
In the discussion that follows, various advantages are described to provide a
preliminary appreciation of the print system and interface. Following this
discussion, a section entitled "Exemplary Embodiment" discusses a specific
implementation example of the print system and, in particular, the data interface.
The data interface described below supports the following capabilities, each
of which is discussed and appears under its own heading: a generic data model,
asynchronous client dispatch, asynchronous server dispatch, cancellation,
batching, transactional invocation, parallel invocation, interception and reflection.
Generic Data Model
The described data interface supports and can be used in connection with
what can be considered as a generic data model. The data model can support
classes of objects with the following capabilities:
• Properties - any object can have an arbitrary set of properties. The
client can specify any set of properties to retrieve from any object.
Only the properties the client is interested in will be retrieved.
• Commands' - any object can support a set of commands with an
arbitrary set of parameters.
• The wire format does not need to be modified in order to arbitrarily
extend the class, commands and properties available from the server
to the client.
Asynchronous Client Dispatch
Asynchronous client dispatch refers to the ability of the data interface
application program interface (API) to allow a client or client application to begin
a data request which immediately returns control to the client thread, as will be
appreciated by the skilled artisan. When the result of the operation is complete,
the client is informed via a call-back or a system event being signaled and it can
the retrieve the result of the data. In the illustrated and described embodiment, this
capability is not provided as a trivial wrapper that spins a thread which blocks
synchronously and allows the client to proceed. Rather, the asynchronous dispatch
is extended down to the device level. At a low level, the result of the call is
interrupt driven by the network card. Utilizing this model simplifies designing
client user interfaces that remain responsive while retrieving data from the server.
It also reduces the number of threads required on a client. This is particularly
advantageous when the client system is a print system acting as a bridge to another
print system.
Asynchronous Server Dispatch
Coupled with the ability of a client to dispatch requests asynchronously to
the server, the server can service requests asynchronously from the client. If an
action that a client requests the server perform is IO bound (e.g., it needs to write
data to a disk, or request data from another server), a plug-in component can issue
another asynchronous call, return control to the server side data interface, and then
complete the call when the IO completes. This mechanism can dramatically
reduce the number of threads running on the server and can ensure that all threads
are fully utilizing the system CPU.
Every thread executing on a system takes a large amount of "non-paged
pool" from the system (non-paged pool is memory mat cannot be written out to
disk when physical memory become limited). Each thread running in the system
that acquires system locks also creates a large amount of "contention" in the
system (this is the amount of time and CPU resource required to switch between
threads of execution). Thus, reducing the number of threads running in the system
is an important mechanism to improve system performance.
Cancellation
In the embodiment described below, calls which are in progress on the
server can be cancelled by the client at any time. This helps a client provide an
interactive user interface to a caller.
Batching
Batching refers to a client's ability to use the data interface to build up an
arbitrary sequence of actions and have the sequence of actions sent to the server as
a unit This sequence is then processed and returned to the client in a single
transfer response. The actions can include any type of action such as getting and
setting properties, querying for a set of properties, issuing commands against
server objects, and registering for notifications about server object properly
changes.
Transactional Invocation
Transactional invocation refers to a batch being assigned the semantics that
it must execute entirely, or not change the state of the server.
Parallel Invocation
Parallel invocation refers to a batch being assigned the semantics that all
items in the batch must execute in parallel. This allows the server to execute longrunning
actions in parallel, and it also provides the semantics that the batch will
execute all actions regardless of what other actions fail.
Interception
Since the operations and properties that the system can support are
represented by pure data transfers, other components can be inserted in the system
that can monitor, synchronously respond to and modify the behavior of the system
- referred to as interception. This allows monitoring software to be plugged into
and removed from the system. It also allows an independent hardware vendor
(IHV) to extend system behavior. For example, if a queue is stopped, an
associated service might also need to be stopped. The "stop" command on the
queue can be intercepted by the IHV and they can then ensure that the service is
stopped before allowing the stop operation to complete. The interception
mechanism can also provide transactional semantics.
Reflection
Reflection refers to the system's provision of a mechanism to retrieve
which -properties are supported by a given class of object, as well as other
information associated with the object. Information that can be retrieved using
this mechanism can include, without limitation:
• the type name, field name and data-type of each property;
• a human-readable description of the properties usage;
• commands supported by an object, and the input and output
parameters supported by the command. Each command has a
human-readable description of the command's usage. For each
parameter, the following data can be retrieved: the name and type of
the parameter and a human-readable description of the parameter.
In the illustrated and described embodiment, the reflection capabilities of
the system are protocol agnostic. In addition, the reflection capabilities can be
used to and from both native code and managed code implementations of server
plug-ins. Further, the reflection capabilities provide the ability to perform
reflection over a network interface, and do not require the implementation of the
server-side objects to be present on the client.
These and other capabilities will become apparent from the description
below.
Exemplary Embodiment
In the discussion that follows, a high level discussion of the inventive
interface is provided in connection with Fig. 1. Following this discussion, an
implementation example is provided and contains implementation-specific
information. It is to be appreciated and understood that the implementationspecific
example is provided as but one example of how one might implement a
print system that includes the capabilities described above. As such, other
implementations can be utilized without departing from the spirit and scope of the
claimed subject matter. The other implementations may or may not be employed
in the context of print systems, as noted above.
Fig. 1 shows a system generally at 100 that includes a common data
interface 102, a batching router 104 that includes a plug-in table 106 and an
interception table 108. System 100 also includes a message plug-in 110, message
services 112, a collection 114, various objects 116, system extensions 118 and socalled
"other collaborations" 120, each of which is discussed below.
Common Data Interface (GDI)
The Common Data Interface (GDI) is the interface to the batching router
104 and allows messages to be built up and dispatched to the batching router. The
GDI is also responsible for sending responses to the client.
Batching Router
Batching router 104 receives messages that are packaged and passed into
the system by the GDI 102. Each message can include a number of operations that
can be destined to a number of plug-ins. These messages are dispatched
asynchronously and the results are retrieved by the batching router 104 in turn.
Messages are routed based on an unambiguous path (hat identifies which
collection the message should be sent to. A single message from the client's
perspective might have multiple destinations.
Plug-In Table
The plug-in table 106 keeps track of all of message handling plug-ins which
are responsible for a given collection of objects. Each collection is identified by a
Globally Unique Identifier (GUID). A path to the object is inspected and looked
up in the plug in table before the messages are passed down to it.
Message Plug-In
Once the appropriate collection has been identified, the set of messages are
passed to the message plug-in 110. The message plug-in 110 might simply send
these messages out remotely over the wire to another machine. More typically,
however, the message plug-in 110 will interpret the messages and respond
appropriately, hi the illustrated and described embodiment, third parties can
provide message plug-ins. However, when they do so, the services provided by
the message services are not available to them. This will make plugging in at this
layer much more difficult. A primary advantage of being a message plug-in is that
the form of the original message is made available. This can be useful for
10
allowing the entire message to be relayed to a remote machine by a plug-in
collection. This could, in addition, be an advantage for an independent hardware
vendor if they also had a remote system that supported batching and they wanted
to be able to translate between the received message format and their own message
format
Message Services
Since one of the main problems in a message based system is the difficulty
in maintaining state over message calls, a component - the message services
112—is provided to break down messages and translate them into a simpler
sequence of calls on a collection interface. By way of example and not limitation,
in the illustrated and described embodiment, the message services can perform the
following tasks:
Assign messages to threads;
Allow messages to be responded to in multiple, deferred calls;
Retrieve the appropriate data from the objects in the collection to
populate the message;
Handle operation cancellation correctly;
Caching of object instances over time;
Transparent locking of objects; and
Reflection services for the objects maintained in the collections.
Collections
Collections 114 maintain a homogenous set of objects. The system will
provide a collection that allows objects to persist themselves easily. One
advantage of using collections is that it allows independent hardware vendors to
easily extend the system with arbitrary objects. In the illustrated and described
embodiment, a collection is implemented as a COM interface that is retrieved
from a dynamic link library (DLL). The DLL is looked up in a Global Assembly
Cache and a direct call is made to DllGetClassObject, which allows one to avoid
COM registration, as will be appreciated by the skilled artisan.
Objects
One goal of the system described above and below is to allow independent
hardware vendors and other developers to largely be able to code at the level of
classes, with direct support for their implementation language. The goal is to
consolidate as much code as possible into the collections to reduce the total
amount of code that independent hardware vendors and other developers have to
write. An object 116 is a logical construct maintained by a collection. The
message services 112 provide a layer where these objects correspond directly to
C++ classes.
Interception Table
The interception table 108 provides a general, universal, extension
mechanism to the print system. The goal here is to allow extensions to be able to
intercept and modify messages targeted to any object in the system, to objects of a
particular class or to a particular obj ect instance.
System Extensions
As most everything in the system originates from a central message system
and is represented by messages, system extensions 118 permit the system to be
extended, and most especially monitored by third parties, in any way they see fit.
These extensions also provide for useful monitoring extension by the spooler team
itself.
Other collaborations
It will be appreciated that not all of the system behavior can be expressed as
a data-driven messaging system. As such, there are and will be other
collaborations 120 between objects that create other sub-systems in the machine.
These can include, for example, pipelines and the scheduler. The goal here is to
make all of the collaborations as flexible as possible to allow other parties to plug
into the system.
The way that these other sub-systems maintain coherency with the GDI
view of the system is by invoking factory objects through the GDI that either
create the object directly, or return the appropriate data to allow the object to be
instantiated. Using this pattern has the result that system extensions can intercept
these calls to the factory objects and they can implement the factory and wrap the
original interface. This means that system extensions can monitor or modify any
aspect of the system. This is very useful to allow plug-able monitoring
components to be inserted in the system at arbitrary points.
Having now provided a high level discussion of the inventive interface, the
following section describes an implementation example contains implementationspecific
information. As noted above, it is to be appreciated and understood that
the implementation-specific example is provided as but one example of how one
might implement a print system in accordance with the inventive principles
described herein. As such, other implementations can be utilized without
departing from the spirit and scope of the claimed subject matter.
Implementation Example
Preliminarily, the following glossary of terms and acronyms will be used
throughout the discussion that follows:
• Action - See Batch.
• Access Adapter - See Data Access Adapter.
• Accessor - A method used to retrieve data from an object. A .Net
property is an example of an accessor.
• Batch - A sequence of Actions that are executed at one point after
being accumulated. A batch will typically result in one network call.
Actions can be Gets, Sets, Queries, Commands and Notification
requests.
• Batching Collection - The highest level interface that plugs into the
Batching Router. It receives an appropriately routed Batch of
Actions to perform.
• Batching Router - The element in the GDI that selects which of a
set of Collections to pass Actions in a batch to.
• Canonical Name - A name that uniquely identifies an object
instance in time and space. In the GDI this will be a collection GUID
and an object GUID. The GDI routes based on Canonical name only.
Applications should use canonical names where-ever possible when
communicating with the system. See Friendly Name.
• CDI - See Common Data Interface.
• Class — In the GDI, a kind of object. A class could be a printer, a job
or a server or any other logical division. A class consists of Types.
• Client Collection - A collection of objects maintained on the client.
The CDI populates the objects in the client collection based on their
Type Maps.
14
• Collection - A heterogeneous set of objects provided by one logical
piece of code. The GDI routes requests to multiple collections based
on canonical name.
• Collection Adapter Service - A service that exposes a Batching
Collection interface to the Batching Router and implements
services to allow a set of objects to be exposed with less effort.
• Command - A kind of Batch action that causes a Method on an
object to be executed when the batch is finally sent to the server.
• Common Data Interface - A component that allows access to
objects via a canonical name. It also provides support for batching
calls together and asynchronous or synchronous access to the
objects.
• Data Access Adapter - A data access adapter plugs into the GDI
and translates its capabilities in some way. This could be to
transform the interface exposed by the GDI to another one (an Inproc
Adapter), or it could be to remote the GDI, in which case it is
a Remote Access Adapter.
• Display Name - See Friendly Name.
• Embedded Types - An object in a Collection that is used to
dynamically extend the properties supported by another object in
another collection.
• Field — A named piece of data.
• Friendly Name - Also known as Display Name. A Friendly Name
is the name that a user associates with an object (Fred's Printer) as
opposed to the Canonical Name that is used by the system to identify
the object instance, hi order to allow Friendly Names to be
converted to canonical names, the GDI provides a Name Resolution
Pipeline.
• Hierarchy - In the GDI it is an arbitrary relationship between parent
and child objects that can be obtained by querying for Links from
the parent object.
• In-proc Adapter - A form of Data Access Adapter that runs inproc
with a GDI instance running in the application. These will
typically just transform the data in some way. For example, by
providing APIs to access the data.
• Link ~ A special reserved Class that is used for querying for
hierarchical relationship between objects.
• Metadata - Not to be confused with reflection in the CLR.
Metadata can be queried from any object to discover the Types,
Fields and Commands it supports. It can be retrieved whether a
corresponding assembly is available on the client or not.
• Method — An actual piece of code on an object that gets executed
when you call that object. See Command for a slightly different
concept.
• Name Resolution Pipeline - A sequence of elements called
Resolvers that run in the application space to convert a friendly
name to a canonical name.
• Optimized Type Map - A sequence of gets and sets to be
performed against two objects to transfer a set of fields from one
object to the other. This is built be combining the fields of a client
object's Type Map and a server object's Type Map.
• Persistent Object Collection Service - A service that plugs into the
Collection Adapter Service that allows objects to be persisted to a
database.
• Query — Jh the context of the GDI, a request for a set of objects of a
given class with an optional filter and an optional Query Base.
• Query Base — The conceptual point at which a query begins. For
example, a base might be the local machine or a given server.
• Remote Access Adapter - A remote access adapter allows the
interface exposed by the GDI to be remoted between machines. It
does this by appearing as a collection of objects to one GDI and a
Data Adapter to the GDI being remoted.
• Resolver - An element in the Name Resolution Pipeline. Each
resolver is associated with a Collection and knows how to identify
elements inside it.
• Type Map - A table of fields and their correlated Accessors that
describes how to retrieve data from an object or apply it to an object.
Type maps can be dynamic or static. Two type maps are combined
by comparing fields to produce an Optimized Type Map.
• Type - Analogous to an interface. A type is a logical group of
Fields and Commands that must always be implemented
completely.
Common Data Interface
The common data interface (CD!) provides functions that include locating
an object by name (friendly or preferably canonical), obtaining data from the
object or applying data to the object, supporting notifications on object changes
and issuing a command to an object. The GDI address and overcomes many
limitations of contemporary architectures including, without limitation, the use of
synchronous interfaces, the lack of extensibility, name based routing and lack of
batching, to name just a few.
The GDI does this by allowing an arbitrary number of uniquely identifiable
collections to be plugged into the system. These collections can support any kind
of object that they wish, The interface itself is reasonably complex in order to
support asynchronous operations and batching, but services are provided and
allow plugged in collections to be written more easily.
Router Re-architecture
A large number of the fundamental problems in current spooler
architectures derive from the router. This includes the use of a synchronous API
•>.
set, the necessity to load all of its providers (and monitors) at start up, the inability
to rename objects, the inability to unload any of the spooler components and the
inability to shut down the system. This section describes a replacement
architecture for routers in the inventive print system.
The following discussion is based on the overview diagram shown in Fig.
2. In this example, the router is replaced by a much more flexible data access
mechanism, the Common Data Interface (GDI). The GDI fundamentally is a
routing and service layer between an Access Adapter and a Collection. It provides
for the ability to locate print objects on any system via multiple protocols and it
also provides a set of services to allow all objects to have the same semantics.
These semantics are the ability to retrieve and set a subset of the objects data, the
ability to query all objects for their status, the ability to issue commands to the
object and the ability to send change notifications consistently from all objects in
the system. The data access Adapters are divided into in-proc Adapters and
remote access Adapters.
In the illustrated and described embodiment, all internal components and
applications (except for Collection Providers and Access Adapters) interface only
with the In-proc Adapters. For communication across process boundaries and
client-server communication the COM/DCOM remote access adapter is used. For
web-service based scenarios, a SOAP adapter is used. Adapters appear in turn as
collections in other process spaces, or on other machines. This allows any access
Adapter that can fully support the Common Data Interface to always be accessible
through an in proc Adapter. The overview of this connectivity scheme is shown in
Fig. 3.
Down-level and up-level Protocol Access
An up-level Adapter is any Adapter that can entirely encapsulate the full
Common Data Interface — the only full in-proc Adapter will be the attribute based
managed code Adapter. The Managed APIs to the system will only represent a
useful application subset of the system capabilities. There will be two types of full
remote access Adapters including the out of proc COM Adapter used for
communication on the local machine and in client-server based scenarios and the
SOAP remote access Adapter which will provide access to the data via a web
service.
Other candidates for Adapters are Adapters that can use the full power of
the Common Data Interface, themselves only expose limited data types, but where
these can be mapped in an entirely data driven way. For example, the mapping
between a MOF file data-type and a Common Data Interface data-type could be
made entirely data driven. MOF files are used by the Windows© Management
Instrumentation (WMI) system to describe system objects.
Other, less flexible, protocols would most likely use the in-proc Adapter to
communicate with the print system components, for example, RFC clients would
communicate with the new print system via the OH- template library,
Support for down-level providers will be simplified by providing an inmemory
collection and allowing a user to write a managed class and object
adapter to provide data to the system. A down-level provider will have to support
a certain limited subset of types to be useable by the system.
Common Data Interface Object Model
In the illustrated and described embodiment, the common data interface
(GDI) has properties as follows. The GDI supports getting and setting individual
properties on an object. It also supports querying for groups of objects based on a
query filter and a set of properties to retrieve from each object. The GDI supports
asking an object to execute a command, passing a set of parameters to it and
receiving a set of return parameters from it. Further, the GDI supports requesting
notifications for object changes. The notification includes a filter and a set of
properties that the caller wishes to be notified about. In addition, the GDI is inproc
only, and the ability to remote the interface is provided by the appropriate
plug-in Adapters. The GDI uses callbacks for notifications; notifications provide
for some difficult trade offs since connections maintained by clients dramatically
reduce server scalability. To solve this problem, two classes of notification can be
used: first, management notifications can maintain a connection to the server
using an asynchronous interface. These notifications will be used for frequently
changing data that tend to have a management purposes, for example a view of the
jobs in a logical queue; second, status change notifications can use a TTL scheme
and a messaging system (e.g. MSMQ) to send less frequent notifications with
delivery guarantees. Since the notification mechanism will support server side
queries, commonly needed user notifications will be implemented by specifying a
server side query and using a guaranteed delivery mechanism.
Further, the GDI is. based on Accessors. It does not expose a dictionary
interface. Rather, query mechanisms are provided to obtain data and the ability to
retrieve object metadata to emulate a dictionary interface. The GDI also
aggregates a number of collections of objects together. Up-level collections are
used to provide foil query support. Down-level collections are used to provide
enumeration support, since all print collections should look alike; a query is built
by enumerating all objects and applying a filter to them. Any method that is not
CPU bound, can be called asynchronously.
The common data interface allows access to collections of objects. All GDI
objects are accessed through a collection. Every collection allows access to a set
of object classes. An object class identifies the class of object, for example, a
logical device, a protocol adapter, a logical server. An object class is identified by
a programmatic name. The programmatic name must be of the form
.[.]. This name can be used in any locale to identify
the object. The string can be implemented as a Unicode string, although in other
implementations it can comprise an English identifier.
In the illustrated and described embodiment, a particular class of object will
always be identified by the same programmatic name over the entire lifespan of
the system, including any previous versions of the server, or any collections
representing a down-level server.
A given object class consists of a number of types. A type is identified by a
Programmatic Name in the same way that an object class is. A type consists of a
number of fields - each field is identified by a programmatic field name and a field
type. The fields in a type must be invariant across all versions of the operating
system and on any platform. A type also encapsulates commands as well as fields.
A command is identified by a string and it takes a number of ordered input and
output arguments.
The GDI will allow the interception and extension of the messages sent
through the GDI by third parties. This will allow these parties to monitor and
extend the object capabilities. Objects will also be allowed to redirect certain calls
to contained objects.
Object Naming
In the illustrated and described embodiment, objects always have a unique
canonical name that identifies precisely that instance of the object - the only
exception to this rale will be for collections that represent objects on a down-level
server.
An object is identified by an object path. The object path always begins
with the GUID of the collection instance in which it is contained, so:
{XXXXXXX}\{YYYYYY} refers to the object with GUID {YYYYY} in
collection {XXXXX}. For objects on an up-level server, the path would be:
{WWWWW}\\{XXXXX}\{YYYYY} where {WWWW} is the remote
collection GUID, and {XXXXX} is the remote collection in which the object
{YYYYY} is located.
Every GDI instance supports the concept of a default collection which is
analogous to the current path in the file system. Thus, a GDI hosted in the
application space can have its default collection set to the spooler on the local
machine, as could all of the satellite sandbox processes and app-domains. Any
object canonical name without a folly qualified path will automatically be
searched for in the default collection. If a collection implemented in. the
application space (for example, direct network access to a server bypassing the
local service) is to be accessed, then a fully qualified path (i.e. one beginning with
a *V) should be used.
In the illustrated and described embodiment, the following are reserved
characters in an object path: '\'t, ':y"VA' and V.
The caret character (A) is used to escape a succeeding character and allows
any character to be represent-able in a string. Valid escapes are shown in the table
just below.
Character Escape
Thus, in order to separate an object name that must contain a 'V in the path,
the 'V could be escaped by 'AV.
Friendly names and name resolution pipelines
Objects can also optionally be given a friendly name (or display name).
Objects should always be located using the following mechanism:
For display purposes, a baseless query for the object class should be
made against the service. All local objects matching this class will
be returned. The UJ from then should use the canonical name of the
returned objects and show the class display name.
For names that might be directly typed into the user interface
(including remote server UNC names), a sequence of pluggable
components called name resolvers can be provided. The application
calls these in order to resolve a "friendly name" to a canonical name.
Existing APIs will use the name resolver to convert friendly name
based calls such as OpenPrinter and ClosePrinter into the canonical
name form (since name resolution is likely to be slow, a caching
layer will have to be introduced).
The sequence of name resolvers will imply the priority of a given
name mapping. For example, when a remote UNC name is upgraded
to a connection, the same name will resolve to the logical queue
object in the local collection rather than the queue object
representing the remote queue.
The application can choose to only have the most full featured name
binding returned to it or to have all the name bindings for a friendly
name returned to it. It will be more expensive to return all the
possible name binding since it might require loading many
collections to resolve.
Having the name resolution elements prevents the entire collection
being loaded unnecessarily, unlike hi the current router architecture.
Every collection wanting to support a friendly name for their objects
will have to implement a name resolving element too.
The application will also be allowed to specify intent when it
performs the name resolution. For example, a management
application might specify that it only wants non-cached information.
In this case, a non-cached object will be returned even if it is less
capable that the cached version.
Security
In the illustrated and described embodiment, security is intimately tied into
the service layer that will be provided by the system. But an overview of how
security will work is presented here because it is an important topic and a global
overview of the model is desirable before the implementation is discussed.
Fig. 4 shows a conceptual diagram of a new spooler security model in
accordance with one embodiment. One goal of the new spooler security model is
to keep the GDI as a lightweight internal interface to objects, so it does not
perform any authentication function. 3h addition, the GDI can route data via a
remote collection to a server. Authorization is then performed by the seryer. So,
in accordance with this embodiment, the GDI routing layer will not provide an
authorization function. Finally, the GDI will also execute in the application space
where neither authentication nor authorization are required.
Thus, authentication is performed by a remote access adapter in a way that
is consistent with the access transport and protocol and authorization is performed
by the appropriate collection service (or deferred to the remote server). One
consequence of this approach is that the GDI and all of the collections can be
access by an un-authorized user. To help alleviate this, a Logical System object
provides an access control list (ACL) that controls access to the entire print system
for the machine. When a batch of commands is received by the remote access
Adapter it performs an access check on the logical system object. The commands
(as well as gets, sets and queries) are only allowed to execute if the access check
succeeds. This operation is performed once for any batch command received by
the remote access Adapter. In the illustrated and described embodiment, the
logical system ACL defaults to "Everyone" (but not "Anonymous"). The
administrator is allowed to change the logical system permissions to restrict access
to the entire print sub-system.
In the illustrated and described embodiment, there is a special reserved
system reset command on the logical system object that the remote access adapter
is required to pass through without authorization to the Logical Machine object.
The Logical Machine Object allows only Administrators to execute this command
and it adds Administrators back to the Logical Machine ACL. This mechanism
prevents an Administrator from denying him or herself permission to the Logical
Machine object which would result in the Administrator never being able to access
the subsystem again.
In the illustrated and described embodiment, every object in the local
collections has an ACL associated with it. An access check on an object consists
precisely in checking access against its ACL. Each object instance has a class
object that will provide a default inheritable ACL for a new object of that class,
and the GDI maintains a collection of the class objects. The administrator is free to
modify the class ACL which will provide a default ACL for any new object of the
given class. This allows the administrator to delegate creation rights to an object to
another user, but still apply a default ACL to the object when created.
In the illustrated and described embodiment, the GDI provides a service
layer to allow a local collection of objects to be written as easily as possible. The
service layer provides the security infrastructure to any object implementation that
uses it for free.
Not shown in Fig. 4 is a permissions agent which recursively applies a new
ACL to all participating classes when a permission changes. This agent is
responsible for propagating changes to the class object to instances of the class
and it is also responsible for propagating any administrative hierarchy changes to
the object instances.
The GDI Accessor Model
One of the more fundamental aspects of this interface is the Accessor
model. This mechanism allows the Common Data Interface and a collection to
populate the caller's data structures. In the illustrated and described embodiment,
the GDI accessor model is unmanaged since the print spooler with which it is
intended to operate consists of an unmanaged core service. Since some
components of the print system will be managed and will want full access to the
data in the print system, there is also a managed code access layer.
The idea behind the GDI Accessor model is that the GDI is a broker
between a collection of client objects and multiple collections of server objects. It
provides an efficient mechanism for the client and the server to exchange this
information using only fields that each party wants to transfer.
As an example, consider Fig. 5 which shows aspects of how this
functionality can be implemented, in accordance with one embodiment, in the
context of a server collection, a client collection and the GDI.
In this example, the server and the client both describe the data they wish to
expose or retrieve via the same mechanism—a type map. A type map describes the
various subtypes and fields that each object supports. For each field, an interface is
provided that knows how to get or set the given property for every instance of the
class that it describes. So, given a type map, all of the properties of a class
instance can be accessed.
Given a description of a client and a server class, the GDI can perform an
elegant optimization. Specifically, the GDI can match each field and type from
the client type map and server type map and create an optimized type map which
consists only of the access interfaces. When this optimized type map is executed
on two object instances, all of the requested data is transferred between the two
objects without any intermediate storage, or any look up of data. The goal of the
optimized type map is to take advantage of the fact that the target objects have a
regular structure. For a given collection, the server side objects have a regular
structure. And, for a given query, the same data is generally desired from all of the
objects in the system. Thus, the binding between a particular class of a source
object and a particular class of destination object is always the same. In addition,
since the source and destination objects are ultimately system components and are
themselves regular structures, each object instance can be stored in a lightweight
way.
Consider further that this would be sufficient if there were only one server
object that data could be retrieved from. In practice however, there could be a
number of implementations of a compatible server object and each will have a
type map. Thus, when the client registers its type map with the GDI, the GDI
creates a storage space for optimized type maps that can be looked up by GUID.
The server side then stores each optimized map as necessary. The goal is that the
client registers its maps at application start up time. The system then builds up a
set of optimizations around that particular client type as the client starts retrieving
data.
Template derived type maps
Creating the access objects for a class instance can result in a lot of code
being written because each instance behind each interface knows how to access
only one property. A good deal of work can be saved by allowing each instance to
access properties in the server and client using data (for example, field offsets can
be used to access fields genetically across many objects). Doing this dramatically
reduces the number of access object implementations that must be coded.
In the illustrated and described embodiment, the GDI also provides a
template library that allows a user to define a class, create a number of methods or
fields and then write a table exposing any fields or methods they want defined.
The template mechanism extracts the type information directly from the class field
types or method signatures and builds the access object instances automatically.
This allows a client (or server) to expose an object without having to write DDLs.
The object can then be supported across any transport supported by the print
system and will automatically receive the benefits of asynchronous invocation.
The code excerpt just below shows the simplest mechanism to define a
structure that the client wants to retrieve from the server, error handling is omitted.
In the illustrated and described embodiment, the template library supports a
core set of mappings from GDI types to C++ types. The library is extendable by
third parties, so new types can be introduced or refined at any tune without
changing any part of the template library at all.
Using the Common Data Interface
The material presented in this section expands upon the material described
above, but does so at a much lower level. The remainder of the document is
devoted to a low level description of the use and implementation of the GDI in
accordance with one embodiment
GDI Types
The GDI type system is designed to support a number of standard types that
the system provides guarantees for (for example, they will work over any protocol
supported by the system and most can be used for queries). The GDI type system
also provides mechanisms to create new types at design time and use them in the
system at run time.
To this end, an ImgVariant type is defined as shown in the code excerpt just
below. Liie the system variant, it has a type and an unnamed union. However it
supports a very different set of basic types to a system variant, as shown by the
table that follows.
(Figure Removed)
Another difference is that the ImgVariant type is extensible. This is
accomplished by defining the LngType not as a union, but as a pointer. This
declaration is shown in the code excerpt just below.
(Figure Removed)
In order to reserve ah enumeration for a new type, one defines an
ImgTypeReservation global variable, and its address becomes a guaranteed unique
identifier for the defined type. Similarly the system will define its own types as
globally unique pointers. This uses the loader to guarantee that the types do not
collide across any address space.
In the illustrated and described embodiment, the print system provides the
following methods to manipulate variants.
(Figure Removed)
New GDI Primitive types
The GDI ImgVariant type itself can contain some new types defined by the
GDI. These are, in order:
IlmglmutableString
This is a ref-counted string. This type will be freely converted to or from a
B STR by the GDI and it provides for efficient manipulation of strings. Its interface
and creation function is shown in the code excerpt just below.
(Figure Removed)
The Value method returns a pointer to the string maintained inside the
immutable string. The caller must not modify the value, but is free to read it. Its
address will not change and it will not be freed or modified while the string
reference is maintained.
The Newlnterface method allows the caller to request a new interface to the
same string. This is useful to isolate reference counting problems. The caller can
request a single threaded interface to the string, hi this case, the reference counting
on the new interface will not be interlocked. If a caller knows that it will be doing
a lot of manipulation of the string in a single threaded context, this can allow them
to improve their performance. The initial string created by ImgCreateString is
always free threaded.
IlmgCanonicalName
This type is used internally and by plug-ins to provide a canonical name
reference to other types. This type is illustrated in the code excerpt just below.
(Figure Removed)
A canonical name consists of the parsed elements of an object path. Each
element is stored as an immutable string. The canonical name can be created either
from a path, or it can be directly constructed from a set of immutable strings. For
example, for the path "\{GUTD}\abcA\def', the canonical name will consist of the
two strings: "{QUID}" and "abc\def". The interface also allows a caller to
construct a path from the substrings of a canonical name, for example, they could
create the canonical name with the strings "ab\\cd", "efg" and then get the path
Like the immutable string, a canonical name is immutable, and you can
obtain new aliased interfaces from it, for threading or reference counting purposes.
IlmgMuItiValuedName
The multi-valued name is used to return a reference by canonical name to a
number of different objects. It is used to represent a 1-N or an N-N relationship
between two object classes. Its interface is shown in the code excerpt just below.
(Figure Removed)
A multi-valued name can be constructed from a set of canonical names and
each canonical name can be retrieved. Like the immutable string and the canonical
name interfaces, it is immutable, and you can obtain a new interface to it for
reference counting or threading reasons.
Since navigating these interfaces could be somewhat complex to a caller,
the multi-valued name provides a mechanism to get the entire enclosed canonical
name as an array of BSTR paths through the GetNamesAsStrings interface. In
order to free the returned multi-string, the ImgFreeMultiString call is used. In
addition, the caller can go straight from a multi-string of paths to a Multi-name.
This could be especially useful for constructing a multi-valued name from a set of
static paths.
Registering new variant types
The standard variants will always marshal correctly over the wire and all
have support in the query language used by the system. However, especially for
the inproc-case, it can be very useful to extend the variant types. This allows one
to create new value types, such as points, or printable areas that do not need to be
converted to strings. Alternately, it allows one to define an interface that can be
retrieved and manipulated as part of a command. To this end, the GDI provides a
mechanism for extending the variants that can be handled by VariantCopy,
VariantClear and VariantConvert.
This interface is shown in the code excerpt below.
(Figure Removed)
Here, one needs to supply in IlmgVariantExtender interface that knows
how to perform all the operations on the types that are registered. One then also
supplies the set of types supported by the interface and the set of conversions the
interface will perform. The system will then extend its handling of variant types to
include calling these interfaces for the types that have been specified.
If conversions are provided to and from the standard variant types, then the
remoting layer will also correctly marshal that appropriate data across the wire.
This allows for use of the type transparently within the same process as the server
object or across the wire.
Registering Type Maps
Once a client type map is constructed, it is registered with the GDI and an
interface is returned to the client representing that type map registration. This
object accumulates the optimized type maps used to bind to various collection
classes. The intent is to allow a process to register all of the types that it intends to
use up front and then to be able to use the given type map from then on. As the
optimized type maps are built, the time required to query data from an object
becomes very small since no fields have to be compared. The full interface for
registering a type map is shown in the code excerpt just below.
(Figure Removed)
Since there are a number of types used for this interface, each one is
explored below.
ElmgCopyFlags
These flags are associated with a copy operation and let the client know
about any issues that were encountered when copying data. Most of the flags are
self-explanatory. Some of (he more interesting flags are:
Private - The data should not be copied outside of the system. Other
processes considered part of the system could receive the data. The data
will never be sent across the wire.
NotModified - Indicates that the data hasn't been modified from that on the
server. This can be useful for optimizing a set operation.
CollectHistory - This indicates to the GDI that the history of the changes to
this field should be collected. The default behavior is to collapse the history
of a field.
ImgExtraCopyData
This is passed to the accessor to allow flags to be modified or inserted by
the accessor. It is also used to pass a change handler to the accessor when a set is
being performed, which allows the change handler to send changes if the property
value changes.
ImgFieldAccess
This specifies a field. Most of the structure fields are self-explanatory. The
description does not have to be specified on the client. On the server, this field can
be used for reflection. The tag is used on the server to allow a particular field to be
referenced cheaply. It is the responsibility of whoever propagates the structure to
keep the tag unique.
ImgFieldAccessors
This specifies a set of field accessors.
ImgClassDescription
This describes a class, specifies the get accessors and the set accessors.
IlmgAccessor
This interface allows access to a particular field. The ImgVariant stores the
data temporarily, and allows many different types of data to be passed,
HrngTypeMaps
This interface can be released by the client. In turn, all of the accessor
interfaces passed in at registration time will be released.
IlnigCommonData
This is the central interface that can be used to register a type map. This
interface can be released and the type map registration will remain valid.
Batching Interface
The remaining sub-sections (i.e. Object Queries, Object Commands and
Notifications) deal with the data access interface in a large amount of detail. This
section deals with what an object is and how the client gets, sets and queries
objects.
The interface used by the client to access these objects, is shown in the code
excerpt just below.
(Figure Removed)
This access interface exposes new concepts which, by way of example and
not limitation, include:
• A batching interface (IlmgBatch)
• An asynchronous programming mechanism (HmgAsyncResult).
• A client collection interface (HmgClientCollection).
Each of these interfaces will be discussed in abstract first and then an
example of a potential call sequence will be shown in order to make these ideas
more concrete.
All actions issued to the data access interface are batched. The batching
interface is returned from a call to HmgCommonData::GetDataBatchO, then a set
of actions are issued to the batching interface, and lastly the batch is executed. If
the caller wants to issue a single action, they just create a batch request of one
action.
None of the actions are executed until the Execute method is called.
Execute can either be called synchronously or asynchronously (depending on
whether Execute or BeginExecute is called). The caller can pass in a completion
interface, or they can use a wait handle returned from the HmgAsyncResult
interface. The rule is that if the caller does pass in a completion interface, then
they cannot retrieve a wait handle. The caller can associate extra data with the call
through the cookie interface. The GDI guarantees that the Done method will be
called in the call completion interface to allow the cookie to be freed if necessary.
The caller must call the EndExecute call to retrieve the result of the batching call.
The caller can open the batch either as a serial, a parallel or a transactional
batch. A non-transactional batch might have a partial success return while a
transactional batch will not. Unfortunately, not all collections will support
transactions (they might communicate to a down-level system that does not
support transactions), and if any of the batch's actions cross to a non-transactional
collection, the entire batch will fail. Thus, in accordance with one embodiment,
transactional batching can be mainly performed inside the system or only once the
server version has been positively identified.
For some batch-able actions like "Pause all printers" it does not make sense
to be transactional since one would rather have half the queues paused than none
because one queue failed to pause. If the semantics of the batch are serial, then the
actions in the batch are guaranteed to be executed sequentially. If the semantics of
the batch are parallel, then the system might execute the actions in parallel, if there
are sufficient resources or if it is otherwise optimal to do so. This means that for
parallel batch semantics any of the actions could fail in any order. Transactional
batches also imply sequential semantics.
Each action in the batch returns an incrementing index. The caller can
ignore the index if they do not need to retrieve the result of the batch call. (It could
be a transactional batch and the status of the batch is the status of the action). The
result of each call can be retrieved by calling the corresponding "Result" method
of the batch interface. If the call reports an error, extended error information can
be returned from the batch.
The synchronous and asynchronous forms of execute are the same except
that in the asynchronous case, the return status is returned from the EndExecute
call. Since batch commands could execute in parallel, a batch can have a partial
failure result. Each index can then be queried to determine precisely which call
failed, and why, if the caller desired,
In accordance with one embodiment, the methods on the batching interface
are summarized below:
GetObjectData - this retrieves data from the given object name using the
given type map and caller specified object
SetObjectData - this writes data to the given object using the given type
map to make the write (the Accessors between the client collection and the
server collection are reversed) and the object to retrieve the data from. Note
that if the data is being sent to a remote destination, the contents will be
copied out early from the object. If the data is being set against a local
object, the contents will be copied out later when the actual set occurs.
QueryObjectData - This issues a query against a certain object class.
Queries are discussed in more detail below. A query is conceptually very
much like a Get command except that many objects will be allocated from a
collection in order to satisfy the query rather than just a single object.
IssueCommandToObject —This issues a command against an object.
Commands are discussed in more detail below. A command is issued to a
particular type on a given object. The command itself is a Unicode string
and it receives an ordered list of input arguments. The output parameters
can be retrieved by the caller through the ResultlssueCommand call.
Execute - This causes all of the commands to be batched thus far to be
executed.
Cancel - This cancels any actions that are currently in progress after the
execute call. It also flushes any commands from the batching interface
before the call was executed.
A batching interface can only be re-used after a Cancel call returns, or after
the synchronous Execute call completes or the asynchronous EndExecute call
returns.
Fig. 6 illustrates a diagram of a batch call command. This assumes that the
HmgDataBatch interface builds up a list of commands to be executed.
Queries, Commands and Notifications are covered in detail in subsequent
sections. Accordingly, a sample code snippet that gets the state of an object and
then changes the name and comment of the object and applies the changes again is
shown below. The code is written in the asynchronous callback form to illustrate
the asynchronous batching interface, but it really does not need this complexity.
(Figure Removed)
Object Queries
This section describes object queries. The interface exposed by
IDataAccessBatch is shown just below.
The typeMap and clientCollection parameters have already been discussed.
The queryString parameter controls the query. If it is null, then the query requests
all objects of the given type as specified by the query base. If it is not null, then it
specifies the filter for the query. A query consists of a query base, a query class
and a query string, The query string is dealt with a little later. The query base is
the root object for the query, i.e. it is the object under which the other objects can
be thought to exist (the query base for a job enumeration could be a printer or a
server object). The query base relationship is not typically hierarchical but
relational (i.e. you cannot enumerate objects two levels below a query base).
However, unlike a strict hierarchy, an object can be located under many different
query bases. This reflects the fact that the underlying store is relational. The main
reason for supporting query bases is to allow down-level enumeration based
implementation of a query to easily find the root object for the enumeration. Query
bases complicate the parsing of a query for the up-level case, however.
A sample list of queries and types is shown in the table just below. Note
that objects in a collection may not use the programmatic name of a collection.
They should use the name resolution mechanism for friendly names and then
internally use the numeric object name to refer to the object The collection is
allowed to be specified using the programmatic name for queries since the string is
more likely to be assembled by a human.
Query Object
{XXXXX}
Note that remote collections are not required to return any objects for a
query against the local machine. It would be too inefficient for them to query all
servers. The query string is a filter that specifies the attributes that the given
object must have. A field takes Hie form:
Programmatic.Type.Name.Version:FieldName.
Future queries might be allowed to access data within an XML document
embedded in the data. These queries would have the form:
Progranmtic.Type.Name.Version:FieldName\Node\Node\Properry.
The valid operators and their precedence are shown in the table just below.
Operator
(Figure Removed)
Fields can only be compared against constants. The following are valid
constants:
• Any integer. Integers may be expressed in decimal or hexadecimal form.
• Any floating point number.
• Any string literal demarcated with quotes. If a quote needs to be a part of
the string, then a BASIC style "" is used.
Valid queries are shown in the table just below. To simplify the semantics
of some commonly accessed objects, there can be well defined query bases that
allow easier interoperability with down-level servers. These will include —
• Print queue queries - Query Base should be the server object that contains
the print queue.
• Job Object queries — Query base should be the queue or server object that
contains the jobs.
Query String
The following constraints apply to a query in accordance with one
embodiment:
If the query compares the field against a numeric type (either integer or
floating point) then the actual field must "be numeric (32 bit integer, 64 bit
integer or double).
If the query compares the field against a string type and the comparison
operator is not CONTAINS then the only valid field types are a BSTR or an
lunglmutableString.
The CONTAINS operator only works on string types. For single valued
strings, it amounts to checking for equality. For a canonical name, it
amounts to checking that the path is the same. For a multi-string, it
evaluates the checking if any of the strings in the multi-string are equal. For
a Multi- Valued Name, it is equivalent to checking if any of the paths
expressed by any of the canonical names in the multi-valued name are
equal to the string the contains operator refers to.
The bitwise AND operation only applies to integral types.
To make this discussion more concrete, the code excerpt just below
presents a sample. This queries for a set of print objects and then enumerates
them. This sample uses the waitable form of the lAsyncResult interface. Again, in
this case this is not needed, but it shows another usage of the interface. Note that
we use a feature of the template library that automatically builds an
HmgCHentCollection and enumerator for the caller. Error handling is omitted for
brevity.
Object Commands
As well as querying for a collection of objects, commands can be issued to
a given object
A command on an object is tied to the type, including any embedded types
from other collections that the object might have. This allows the command set of
an object to be extended or a per-instance basis.
In accordance with one embodiment, commands have the following
properties:
• Commands are never cached, data can be cached.
• Commands can return parameters to the caller beyond a failure exception.
• A command has a fixed set of ordered input and output parameters.
Commands can not be overloaded or extended. The semantics of a
command can never change across new versions of the system. (Commands
might be made obsolete and eventually support maybe discontinued.)
Commands will be used for object construction and for object deletion. For
object deletion, a predefined type and method is defined
"Microsoft.GenericObjectl", command "Delete". The delete command takes no
parameters and returns no parameters. All delete-able objects are required to
expose this command. All embedded objects are also required to expose this
command. When the containing object of an embedded object is deleted, it will
request all of the embedded objects to delete themselves.
Another command that all securable objects will be required to expose is
"MicrosoftGenericObject.l" command "AccessCheck", this will take a single
parameter - a permission object and return true or false. This command is used by
embedded types to ensure that they enforce the same security semantics as their
embedding object when they are accessed through their own collection.
The lifetime of the parameters returned from a command call result is the
same as that of the batch. When the batch is Released, Canceled or another action
is performed on the batch then the command parameters will be cleared up.
Finally, a sample usage of the command interface is presented just below.
This requests a stream object from an already discovered print queue.
Notifications
Notifications can also be received from an object, an object class, a server,
or a collection. This interface is also batched from the client. The interface is
shown just below.
This interface exposes a number of new concepts among which are
included, by way of example and not limitation, a notification collection, a
notification object, notification filters and a notification channel. This interface
60
extends the batch interface with another two other actions
RegisterNotificationCollection and ResultNotificationCollection.
A notification collection is a client collection with slightly richer semantics.
It is a collection of homogenous client objects. Each object can be described with
a type map. When a notification for a particular object change occurs or if an
object is added, the object is retrieved, and then populated. When the new data for
the object has been placed in it, the EndNotifyChanges method on the object is
called allowing the implementer to perform any processing necessary once the
obj ect has been updated.
The notification collection specified by the caller will always be updated
serially (i.e., it could be called by multiple threads, but only one thread will call it
at a time). The notification collection will only be updated when the Populate or
Update calls are made on the channel. Thus, the caller can protect their objects by
placing the appropriate locks around the Populate or Update calls, if they are
performing multi-threaded access on their objects. If the caller specified a callback
for the notifications, then only a single thread is guaranteed to execute in the
callback at a time.
Notifications work in the following way. When the Populate call is made
on the channel, the objects will be allocated from the specified collection through
the Add method. Then the objects will be called with the BeginNotifyChanges
call, and whether this is an update cycle or a populate cycle at this time will be
passed into the object.
Then, notifications are received through the notification channel. If the
caller specified a callback, then the callback will be called when new updates are
received (or when the channel develops an error). If the caller did not specify a
callback, then a wait handle will be available or the channel can be polled for the
IsCompleted method. The notifications are received by the subsystem, but are not
applied to the client objects until the Update method is called on the channel. If the
channel broke down for some reason, then an update will return an appropriate
error.
During the call to Update, 'the client objects will be updated with whatever
properties have changed on the server. If any new objects are either created on the
server or if they now match the notification filter, then the collection will receive a
call to the Add method and will be told if the reason for the notification is that the
object now matched a filter or if the object is new.
Similarly if any object now does not match the filter, or if they have been
deleted then the Remove method on the collection is called.
The objects are updated through direct calls to their properties. The updates
will be bracketed by calls to the BeginNotifyChanges and EndNotifyChanges
methods.
When the caller wants to stop receiving notifications, they release the
INotificationChannel interface that they received.
Sample code that receives notifications for two fields from all local objects
in a collection is shown just below. This sample code receives the changes for the
Status and Name fields for all of the printers in the local collection. It is assumed
that the printers are updated on the screen as a result of the query. Error Handling
is omitted for readability.
Client Collections
One aspect of the GDI interface, namely, the choice to populate a client
collection and client object rather than write the data into some sort of
intermediate data object and propagate the data set carries with it some
advantages.
For example, doing so allows notifications and data population to be treated
very similarly. What this means from a client perspective is that client objects can
be re-used very easily between these two cases. Further, doing so allows the client
code to be extremely efficient. Specifically, once the data has been collected into
a client object, it is referenced natively as an offset within a structure. If the data is
stored in an intermediate collection, then either the client has to in turn copy the
data into some client objects to manipulate it, or the data has to be retrieved each
time from the collection.
Further, populating a client collection and client object limits failure
conditions in the client code to when the data is populated, not when it is copied
from the collection. This has the potential to substantially decrease the complexity
of the client code. When an intermediary collection is useds there is a potential for
failure whenever the data in the collection is accessed.
65
In addition, populating a client collection and client object allows multiple
kinds of intermediate collections to be used. Essentially, -this mechanism
encapsulates a completely flexible builder pattern. This means that a generic
"client" can build a type map dynamically and map it to any sort of intermediate
collection it likes. This could be used to build a Data Set object for a managed data
provider. Alternately, it could be used to build an XML view of the data. This can
be done without any loss of fidelity or performance caused by the interjection of
an intermediate dataset. Further, a related property is that populating a client
collection and client object allows a client collection to actually be an access
Adapter that prepares the data in a form that can easily be remoted. On the
negative side, however, it can force the client object to expose accessors for each
property that it wishes to retrieve.
Dynamic Type Maps
The illustrated and described embodiment can be utilized to address the
situation when a user wants to have one object instance, but wants to be able to
retrieve different fields dynamically from the server objects depending on the
calling conditions. There are two cases where this could occur—one, when there
is a concrete object with many properties, but the actual properties the caller wants
propagated in a given call are dynamic; and, the other is when the caller truly
wants to treat the data completely dynamically. Each has a solution in the
illustrated and described architecture.
To solve the first problem, the illustrated and described embodiment uses
the tags that are associated with each property to build a subset dynamic type map
from the class. These tags can also be specified as part of the template library. The
template library will retrieve a partial type map matching only the tags specified.
This solution is shown in the code excerpt shown just below.
(Figure Removed)
Allowing a completely random set of completely run-time determined
properties to be retrieved from a remote server can also be accomplished through
the type map interface. In this case though, one cannot have just a set of properties
bound to a class. It is in this case that the power and flexibility of the low level
type map interface really comes into play. In this case, the caller can take
advantage of the fact that every interface is also an object instance and use the
variant type directly. This notion is shown in Fig. 7.
There, each interface actually points to an object, rather than to a static
method or field. Each object maintains some sort of state about how it should
manipulate the final destination object. In this case it maintains the index into the
objects array that the destination objects maintain. The size of the destinations
object's array is variable and both the delegate objects and type maps can be built
dynamically. Thus, this mechanism allows to caller to bind to any source object,
and allows any source object to be built on the fly.
In the illustrated and described embodiment, this mechanism is actually
completely generic. There is no particular reason why the interfaces cannot
participate in building an XML document, creating a dataset or writing data to the
screen. It can also be used to build collections of objects in any form. This is
extremely useful when bridging to the longhorn APIs or other data access APIs.
The code to implement this scheme is shown just below, including
accessing one object through the batch interface (error handling omitted).
(Figure Removed)
Attribute Template Library and Inheritance
One of the interesting aspects of the template library is that it supports
inheritance (including multiple inheritance) between objects. This is less useful on
the client than it is in the server. On the server, this allows common functionality
and the corresponding fields and methods to be stored in a base class and pulled
into the derived class as necessary.
In addition to supporting inheritance, the library also correctly handles
virtual methods. That is, virtual methods in the base class can be referenced in a
base class type map and then will correctly call the derived class in the derived
type map. An example of a working set of types is shown in the code excerpt just
below. This code just shows the structure of the classes—any implementation is
omitted for brevity.
In this example, the derived class overrides one virtual method and
implements two pure methods. All of the other fields and methods are pulled in
from the base classes. The derived class's methods are still called even though the
definition of the table is actually pulled in from the table defined in the base class.
The derived table pulls in the -definitions of the base class tables with an
"Inherits" field in its table. Any extensions to the base classes and base class tables
will be immediately reflected in the derived class's tables. Thus the tables can be
shown to implement true multiple inheritance.
Cursors
A cursor is an interface that allows a caller to retrieve data from the server a
chunk at a time, rather than as a single request. This is especially useful when
there are a large number of objects on the server that need to be enumerated. This
reduces the amount of data that must be returned across the network in one call
and it reduces the working set on the server side. For very large collections of
objects, it can also allow the client to avoid having to store all of the objects in
memory. Instead, the client can maintain a cursor into the table and retrieve more
rows as required. In this particular implementation example, cursors are not
currently implemented in the GDI for a number of reasons:
The number of objects maintained on the server isn't nearly the same order
of magnitude as that stored in a typical database. It is extremely unlikely
that a client will not have sufficient memory to store its copy of the data.
Many objects on the server are by necessity active because most queues
will receive jobs. Thus, working set is less of a consideration than in a
database.
The network traffic can be arbitrarily reduced by doing the query in two
passes, an initial query which retrieves'the canonical name for each object
and then a second set of GetObjectData calls to retrieve the data for each
object. Since the GetObjectData calls can be chunked together arbitrarily
you don't need a network round trip to retrieve data from each object.
Since the caller can specify an accurate query filter, the size of the data sets
can be made much smaller. For example, the client spooler can enumerate
on jobs belonging to the given user, rather than having to enumerate all jobs
and then filter out those that don't belong to the given user.
Since the caller can specify precisely what fields they want to retrieve, the
amount of data can be much reduced over the current system with its
inflexible info level system. For example, currently to retrieve the number
of jobs in a queue, the caller must specify a PRINTER__INFO_2, which also
contains the default DEVMODE and the security descriptor.
The interface is currently stateless at the wire level except for notifications.
Cursors tend to result in more server state. It might be that the overhead of
rnaintaining the state necessary for a cursor exceeds the benefit derived
from it, especially given the number of mitigations.
Nonetheless, it might become desirable to implement cursors at some point
in the system, especially if the print system is scaled to do document archival, or if
a very large print server can be made to handle a very large number of queues
where a small number are active (avoiding pulling objects into memory
unnecessarily becomes very important in this scenario). This section describes a
mechanism that would result in a fairly cheap forward only cursor for queries.
With regard to notifications, consider that notifications would require no
change to the client interface. The wire interface would send the populated data to
the client in chunks and then would start updating the client objects. For optimal
performance, the updates might be interleaved with the original populate.
With regard to a client cursor, consider the following. One goal of the
illustrated and described system is to maintain a stateless wire protocol to
whatever extent possible. Hence, we define what set of objects are retrieved by a
cursor and then provide an implementation that fulfills these goals. Reasonable
semantics for a cursor are as follows:
• The cursor moves forward only.
• The set of objects that will be retrieved by the cursor is the number of
objects matching the query when the cursor is created, less the objects that
are deleted subsequently. It does not include any objects that are created
after the cursor is first created. This is to prevent a slow client from never
terminating against a server with a rapidly changing set of objects.
The described solution is to keep an ever increasing sequence count with
each object. When the cursor is established, the current maximum sequence count
number is remembered by the client. The set of objects are returned and the
maximum sequence count of the last object is remembered. When the next set of
objects is retrieved it amounts to this query: retrieve the next CHUNKSIZE
objects where Timestamp > lastjmax and Timestamp It might turn out that the overhead of executing this query for each request
on the server exceeds the average overhead of maintaining the necessary
connectivity to maintain the cursor context on the server. A compromise solution
is to keep a cache of cursors on the server identified by a GUTD. The client
submits the timestamps and the QUID to the server. If the server side cursor still
exists in cache, that state is just used. Otherwise, it is recovered by executing the
timestamp query and re-inserted in the cache. This allows the server to discard the
state cheaply for slow clients.
If one assumes that the main goal is to reduce the size of the individual
network requests and to reduce the working set required on the server for queries,
then the GDI interface would not necessarily require any changes, the Remote
Access Adapter and the Local Access Adaptor would just use a cursor to the
server side transparently when the client issues a query.
./For very large datasets, the client might want to manipulate the cursor itself
so that it does not have to keep all of its state in memory at once. In this case, an
interface representing the cursor would be retrieved from the GDI interface. It
would not be necessary to make this call batched since by implication a single
cursor retrieves a large amount of data.
Friendly name resolution
At various points the notion of converting a friendly name to a canonical
name has been discussed. The friendly name is a human readable version of an
object instance, for example, the notion that there is a server called
server.mydomain.org is a friendly name. The canonical name is a completely
unambiguous name that identifies the protocol use to communicate with the
server, the physical server on which the logical server is hosted and a completely
unambiguous reference to the logical server object instance. For example, the
above friendly name might correspond to the following canonical name:
"{63315ca9-bef6-4cc5-9152-05e369d3d691}\www.realhostnet\{a4b6e956-deeO-
46dO-b82c-7ffc8f814e95}\{ fD91b5al-ad2a-4dfe-9d37-9592blae2512}". The
canonical name has the advantage of being completely unambiguous, which is
excellent for a computer. However, it is not readily input by a human.
Given a canonical name, it is easy to determine the friendly name of the
object if this is relevant. The object will simply have a property such as
"ObjectName" that can be retrieved from the object. However if the system has
only the friendly name then it must be able to determine the canonical name. The
application callable side of this interface is discussed in this section. Implementing
a name resolver is discussed below in the section entitled "Friendly Name
Resolution Plug Ins.". The client friendly name resolution interface is shown, in
accordance with one embodiment, just below.
When a caller wishes to obtain a friendly name resolver, they call the
GetFriendlyNameResolver method on the nmgComrnonData interface. The
friendly name exposes only two logical methods (and an asynchronous calling
pattern for one of them):
ResolveName
Given the name to be resolved, e.g. "server.mydomain.org" and the class of
the name, e.g. "Server", the name resolution interface calls the friendly name
resolver plug-ins and returns all the valid results returned by the plug-ins as an
IlmgCanonicalNamelterator interface. The name iterator allows the caller to
retrieve all the canonical names that correspond to the friendly name. The reason
for allowing multiple canonical name returns is that the same object might be
visible via different paths. For example, the same server might be accessible via
both SOAP and RFC.
The ResolveName method can take a long period of time since it might
have to query multiple protocols to see how a particular object can be accessed.
Thus, the interface also provides an asynchronous invocation pattern in the form
of the BeginResolveName and EndResolveName methods. These use the same
asynchronous calling pattern as used by the batch BeginExecute and EndExecute
methods. The caller can either specify a callback interface or can get a system
wait-able handle returned via the HmgAsyncResult return. They will be notified
when all of the corresponding canonical names have been resolved.
RemoveName
The name resolver maintains a cache of resolved names to friendly names
since name resolution is potentially very slow and expensive. The cache will
automatically be flushed over tune. And if any name resolution subsequently fails
to resolve the same names as are in the cache those name associations will be
removed. The behavior is that after a certain amount of time, a name is considered
stale. If the caller requests the name again, the stale name is returned by the name
resolver, but it issues another round of name resolution to the name resolver plugins,
if none of them return the name, the name will then be removed from the
cache. This provides a very fast resolution on names, but it could result in a stale
name binding being returned for a period of time.
The caller might resolve a friendly name and then independently determine
that the name is stale since the canonical name fails when it is accessed via GDI. If
they were to request the same friendly name again, they might get the same stale
name back. In order to prevent this, the stale name can be explicitly removed from
the cache through the RemoveName method. If the caller doesn't do this, the name
will eventually be cleared, it is an optional optimization.
Extended Object Behavior
The goal of the previous section is to present the interface and basic
concepts of the GDI. The goal of the presentation was to expose it mainly from a
usage stand point The interface as presented in the previous sections is powerful
enough to allow any application writer to get, set, query and receive notifications
for objects in the print system. The focus of this section is to present the extra
behavior that can be exposed by objects to enable new security and discovery
capabilities.
Object Factories, Object Cloning
This section deals with the mechanism that can be used to create new
objects in the system, transfer these objects (if applicable) to another system and
allow a new object creation user interface (UI) to be inserted. All objects in the
system except factory objects should support this mechanism. The mechanism is
shown in Fig. 8 and serves as the basis for the discussion below.
The object creation and cloning mechanism assumes that there is a driver
store that can:
1. Provide a UI assembly to the client or the server based on the UI assembly
strong name.
2. Provide a collection assembly to the server side based on the collection
assembly strong name.
The precise definition of a strong name and how it relates to versioning is
another discussion. But, for contextual purposes, consider the following.
A strong name is a name which both uniquely identifies the actual instance
of the assembly which is in use and the publisher of the assembly in one moniker.
One implementation of this is to sign the contents of the assembly using Public
Key Cryptography and then take a cryptographic hash of the signature (typically
an MD5 hash) and embed the hash (or a part of the hash) into the name. Not only
is this name actually tied to the contents of the assembly, but it is also tied to the
publisher of the assembly. There is no known technique whereby someone could
create another assembly with different functionality and use nay assembly's strong
name without creating a very large number (103B for a 128 bit hash) of assemblies
and attempting the entire process on each one.
The essential idea is that each collection provides a well know object
instance that acts as the factory object for a set of object instances. Since the
construction parameters for a new object cannot be known in advance, the object
implements a custom type (Company.Type. 1 in this example) with a command
called CreateQ that creates a new object instance. If the caller knows the factory
object and the factory type, they can create an object of that class in the new
spooler.
Cloning is supported by the object supporting the Microsoft.Object.l type.
The fields supported by the object include the Collection SN, the factory object
instance used for its creation and three commands, GetStream, Delete and
UpdateFromStream. GetStream retrieves the contents of the object into a stream,
Delete deletes the object, and UpdateFromStream updates the object's state based
on the stream passed to it. The object can support any type of stream, but for
interoperability reasons, the stream should ideally be XML.
When an object is to be cloned, the object instance is queried for its
collection SN and Factory object instance. These are stored in a stream, and
subsequently the state of the object as represented by a stream is stored.
To restore an object, the collection is retrieved from the Driver Store and
the factory object is located. The factory object is the passed the remainder of the
stream through the CreateClone Command. This causes the factory to create an
identical object instance.
Note, that all objects in the system should support decoupled UI creation
and cloning. However, for brevity the factory objects and cloning interface are not
shown in the remaining sections to simplify the discussion and diagrams.
Hierarchy
Hierarchy typically accomplishes two goals. First, it subdivides a search
space to allow for efficient location of objects. The division of the GDI into
multiple collections, the ability to only load a collection if it supports the given
class and the ability to direct a set of actions to a given server already
accomplishes this goal. Second, it provides for manageability and discovery of
heterogeneous objects by allowing the system or an administrator to group objects
in a logical hierarchy. Hierarchy enables discovery since every object is located
somewhere in the hierarchy. Thus a browser can be built that allows a user to drill
down into each object in turn until all objects in the system have been discovered.
The hierarchy can also provide a view of the security of the system by allowing
security changes to be applied to a sub-tree of objects by one administrative
action. This mechanism is analogous to how the file system implements security.
Hierarchy is typically not useful for showing the relationship between
objects. A pure hierarchy only allows an object to be located under precisely one
other object. Thus, a job .cannot belong to both a printer and a server. Hierarchy
also cannot efficiently show a many to one relationship between objects. A
relational model is much more powerful for indicating object relationship. The
GDI effectively allows this through the QueryBase mechanism of the Query action
on the batching interface (which returns the objects mat can logically be
considered to be contained by another object).
Since the first goal of hierarchy is already accomplished by the GDI and
remote access adapter routing mechanism and since the relational mechanisms of
the GDI are more powerful anyway for representing object relationships, we only
require a method to support administration and discovery of heterogeneous objects
in the system. Note that since the existing model is powerful enough for a large
proportion of cases, not all objects will necessarily support hierarchy. The default
persistable store provided by the service layer will support hierarchy
automatically. Thus, the simplest implementation for local objects will
automatically support hierarchy. There will be cases where we will support
discoverable hierarchy, but not modifiable hierarchy. For example, a remote
Adapter supporting the current spooler RFC interface might allow a client to
discover printers under servers and jobs under printers, but won't allow the
organization to be changed.
Hierarchy can be expressed by introducing a new Class called the Link
class which has one type, the link type with one property Object which is the
canonical name of an object which can be considered "beneath" the current object
in some way. When one wants to rind out which object instances can be traversed
to hierarchically from the current class, one queries for the Links class with the
object instance that is being traversed from as the query base. An example of a
number of objects set up in a hierarchy is shown in Fig. 9. This excludes the
logical system object, which will be the root object on each and every machine.
The implementation of the links class query will be provided automatically
by the persistent object collection service. But, any collection that wants to expose
a discoverable hierarchy can populate the links query. For example, a collection
that exposes DS objects could enumerate the children of each DS object and then
populate the Distinguished Names of each object into the links tab when the parent
object is enumerated.
Security and Hierarchy
Hierarchy provides for discoverability and security. The GDI provides a
security agent which, when the security descriptor of an object is changed, will run
through child objects to enforce security inheritance rules. Security will
automatically be handled by the persistent object collection service and the
security agent will run automatically on objects in this collection.
A special object called a logical group will be provided that an
administrator can place any set of objects under. This allows the administrator to
change the ACLs on a logical group and have them automatically apply to all
objects under that logical group. This allows an administrator to apply an
administrative hierarchy on their server that they find useful and then apply the
security rules to that hierarchy.
Security and Class Mix-ins
As well as controlling security by hierarchy, the system will also allow
security to be controlled by class. This allows features such as providing
Creator/Owner administrative access to all jobs created on the system. In the
current system, the class rules are hard coded into the system, in the future; mixins
will be available for any class.
As shown in Fig. 10, class inheritance is accomplished by exposing editable
class objects in the security hierarchy. These will each contain initially a default
Security Descriptor that will apply to each class on creation. Like all other objects
they will inherit their descriptor from the logical system object that is used as the
base object for the entire system. When an object of the appropriate class is
created, both the parent object and the class object are used to derive its security
descriptor. Fig. 10 also shows the effect of logical groups on the hierarchy. Here
the administrator has created two logical groups, one to be able to control the
access to any cluster server and another to control the access to a printer on the
local server object. Although only one object is shown in each case here, any
number of objects could be placed under each logical group.
Metadata
In the illustrated and described embodiment, the GDI provides the ability to
bind any object to a remote object, but the caller must know the fields and
commands that the object supports before it binds to it. Most of the time, the client
knows the server objects and the interfaces that they support. If the client did not,
then it would not be able to function correctly since it requires a-priori knowledge
of the server object to function. The interface is geared towards optimizing this
behavior, and classes that precisely know the context in which they will be called
do not need to implement any metadata mechanism.
However, there are also cases where the client does not have a-priori
knowledge about the class it is manipulating. For example, the client might be an
access adaptor that translates the GDI calls into a Managed Data Provider
interface. In this case, the data requested by its client cannot be known a-priori and
the server objects that it is communicating with also cannot be know a-priori. Or,
the client might be a generic administrative UI that allows all of the objects on the
client or the server to be browsed and manipulated regardless of the object types.
To this end, the illustrated and described embodiment provides standard
system types that allow a caller to query an object instance for its metadata. These
types are the Type, Property, Command and Parameter types. Classes
implemented using the collection adaptor service will automatically gain full
metadata support for free. This will be retrieved from the server object type map
that they will supply for each of their classes.
The types that can be queried with the object as a base to retrieve metadata
are shown in Fig. 11.
Metadata is retrieved by first querying for the Types type with the object as
the base of the query. The Types type provides a list of all of the types supported
by the logical object instance. Since it is possible that in the future the system will
allow objects to be embedded in or extend other objects, the types will include all
of the aggregated types for that object instance, not just the underlying basic
object. Each Type type can then, in turn, be queried for the commands or
properties it supports. And, then each command can be queried for the parameters
that it supports.
Query Bases
Query bases are an unusual design aspect in an otherwise un-hierarchical
system. This section discusses why they are implemented at all and how they map
to relational queries.
A query base specifies a source object, which the instances of the class
being queried can be considered "Under" by some measure. This is provided for a
number of reasons. First, it allows an enumeration of sub-elements to be
constructed by someone who otherwise does not know how to express the
86
relationship as part of a query. Second, it maps very well to down-level systems
that are based on enumerations. The base of the query becomes very naturally the
parent object in the enumeration. Thus, for down-level objects, a query base and a
filter on the object state is a natural way to implement queries. Third, for objects
that have highly variable composition (for example the metadata stored by an
object), it is more logical to treat the query as a sequence of enumerations from a
parent object.
However, query bases need some manipulation to map to a relational query.
Fortunately, this is relatively easy to accomplish.
For illustrative purposes, the table just above illustrates a number of jobs in
a print queue. There are two fields (implemented as foreign keys in the underlying
database) that store the canonical name of the object with which the job is
associated. Each column that references a remote object knows the remote object
class.
When a query comes in for a particular base object, the class of Hie object is
correlated to a logical field in the object and then the query is extended to include
this field having the correct value.
87
For example, if a query came in for Base = "{SSSS}", Base Class =
"Server", with the query string "Jobs:Size to the following query:
Jobs-Server = "{SSSS}" AND (Jobs:Size This would return the correct result, Fred's Job. This translation will be
performed automatically in the collection adapter service. It will require slightly
more metadata in the server object type map since we will need to know what
fields can serve as query bases and what classes they correspond to.
GDI Implementation
The previous section describes the behavior that is expected from objects
and describes the interface exposed to clients. This section describes the
implementation of the server side of the GDI. It describes the services provided by
the GDI, and the plug in model.
Fig. 12 shows the major components of the GDI, as well as various plug-ins
that are inserted into the system. Preliminarily, a description of each of the major
components will be provided and then, a detailed description of the components in
the GDI box itself will be provided. The collection adapter service is provided by
the system, but it is considered a separate service hosted by the plug-in and is not
part of the GDI per-se. The collection adapter service is discussed in the next
section.
The Application
The interface to the application has been the primary focus of the previous
sections. The application requests a batch interface from the GDI, records a
number of actions and then executes them. When the execution is complete, the
application can get the results.
The Router
Executing the actions results in the batch being routed by the router and
passed to the plug-ins. The router finds contiguous ranges of the batch that are
addressed to the same plug-in and passes those ranges to the plug-in in one call.
Thus, the plug-in can still preserve batching behavior itself. Depending on the
semantics invoked by the application, the sections can be passed to the plug-ins in
parallej, sequentially or a transaction object can be associated the batch to allow
the plug-ins to coordinate a transaction.
The Plug-Ins
The plug-ins are the interface exposed by a DLL in conjunction with the
DLL manifest and configuration information. A single DLL can expose one or
more plug-in interfaces. In me illustrated and described embodiment, the plug in
interface consists of one factory interface with one method, and the plug in
interface (IlmgBatchCollection) with only two methods - AsyncProcessRequest
and Shutdown. The plug-in interface still receives its operations as a batch. It is
thus the logical interface to use for remoting to other systems that also support
batches. To this end, in the illustrated and described embodiment, the following
plug-ins are provided: a Local Access Adapter, a Remote Access Adapter, a
Sandbox Adapter, and an Adapted Plug-in, each of which is discussed separately
below.
The Local Access Adapter provides access to the local service or a per-user
service. Currently this uses asynchronous COM, but there is no particular reason
why any other rernoting mechanism could not be used.
The Remote Access Adapter provides access to a remote machine and will
largely use the same mechanisms as the Local Access Adaptor, with some
differences to reduce per-client state on the server.
The Sandbox Adapter is used to preserve the work of the application insofar
as grouping the requests into batches. This also means that the same universal
plug-in model can be used in-proc or out-of-proc components, and even that we
can sandbox system components if we wish. All parts of the system aside from the
GDI will be exposed as plug-ins.
The Adapted Plug In is extremely simple in form, but it requires the plug-in
to interpret the queued up batch operations, in an asynchronous call, which can be
complex. For this reason, the Collection Adapter Service (CAS) is provided. The
Collection Adapter Service allows multiple object collections
(HmgObjectCollection) of objects (HmgObject) to be plugged into it and it, in
turn, interprets the batched calls and translates these into calls on the supplied
collections and objects. The Collection Adapter Service provides efficient
handling of both synchronous and asynchronous implementation of these
interfaces. The collection interface is broad enough that it can be used to access
down-level protocols. There is no loss of performance in these cases since they
typically do not support batching.
The Services
hi the illustrated and described embodiment, the GDI provides a set of
services. Most of these are only accessible from plug-ins. These services are
public and are equally exposed to the CAS and any other plug-in. This allows
independent software vendors or independent hardware vendors who find the CAS
too limited to attain the same functionality themselves. An explicit goal is to allow
the CAS to solve most scenarios and, all of the system supplied objects will use it.
The services provided by the GDI include a query compiler, work flow,
transaction, change scope, variants and type maps, each of which is separately
discussed below.
The query compiler is capable of compiling the queries supplied by the
application into one of two forms, a parse tree and filter. The parse tree is useful if
the plug-in wants to translate the query into a query of another form—which is
easier than trying to re-parse the query yourself because brackets and operator
precedence will have been resolved and some basic well-formed-ness checks will
have already been performed by the parser. The filter can be bound to an object
and provides a method - "DoesMatch" that allows a caller to check whether a
given object's state matches the query that was passed in.
The workflow is an interface that is instantiated whenever the application
executes a call into the GDI. It corresponds to the logical execution of a user
action. A work flow can schedule work, and will notify work items associated
with it when the work flow is cancelled (typically because the original call from
the user is cancelled). A work flow can keep track of a set of tokens to detect and
prevent recursion. When a plug-in wants to make a call into the system as a result
of a call from the GDI, it must use the same work flow as in the originating
does this through a special server-side only call
(HmgCommonDataServer::RecurseDataBatch).
When performing a recursion, it must insert a token into the flow that it can
use later to check if the same operation re-occurs on the same flow, and hence
back out of the recursion. The work flow will also keep track of the original
caller's token. When a plug-in asynchronously and independently calls into the
GDI, it can do so either through the client interface, or it can create its own work
flow and call RecurseDataBatch.
If the caller requests a transaction, this object is created and associated with
the batch. The transaction provides a mechanism to associate one transactional
collection and a set of dependent items with the transaction. It provides both a
synchronous and an asynchronous interface to the transaction. When the batch
finishes executing, the transaction is committed. The results are sent to the store
and if the store updates correctly, the dependent items are run. hi order to qualify
as a dependent item, an item must not be able to fail the commit request. This is
used to make live in-memory objects dependent on a state change being
propagated to the store. If the plug-in wants to extend the transaction to include
other objects (for example, in order to implement a "Delete" command that deletes
other objects in turn), then it can specify the same transaction when calling back
into the GDI via RecurseDataBatch. All the changes through each recursion will
be held pending until the outermost batch completes successfully.
A change scope pertains to server side change handler that represents each
channel. Change scopes can be inserted on transactions.
Variants have been covered in previous sections. Variants may be used by
either the client or the server side.
Type Maps have also been covered in previous sections. The optimized
maps are only visible to the server side.
Plug-in interface
In the illustrated and described embodiment, plug-in interfaces are
instantiated using a COM-like object instantiation model. It does not require the
plug-in to register its class ids in the registry. The process of instantiating a plug-in
is shown in Fig. 13.
In the illustrated and described embodiment, all of the plug-ins are stored in
WMLConfig as part of the GDI configuration. Some of the plug-in data will be
synthesized by WMLConfig from the plug-in DLL's XML configuration files (this
is useful for a XCOPY based solution). The GDI configuration consists of a list of
Plug-In DLLs. For each DLL, we list the collections GUIDs that each supports, as
well as the classes supported in each logical collection. Each also has some
associated initialization data. This will typically represent the location that the
objects supported by the DLL will be persisted in and retrieved from. When the
router wishes to instantiate the plug in interface, it retrieves the collection QUID,
finds the corresponding DLL and then calls DllGetClass Object:
HRESULT
DllQetClaBsObject(
We then request CLSID_IImgBatchCollection and
HD_nmgBatchCollectionFactory. The DLL then returns a class factory that can
return the various instances of IlmgBatchCollection supported by the plug-in.
IlmgBatchCollectionFactory supports the following interface:
The factory is passed the following information during Createlnstance:
An nmgCommonDataServer interface. This interface inherits from
HmgCommonData and also includes some plug-in specific methods that
access server side services. Note: The plug-in should not call
CoCreateInstance(CLSro_IImgCornmonData)} since this creates a client
interface. The system shuts down when the last client interface is released,
it will not shut down if a server side plug-in holds a client interface.
A collectionGUTD. This is the same GUID that is present in the GDI
configuration. The factory could choose to instantiate different
IlmgBatchCollection interface depending on this GUID.
Initialization Data- This data will be retrieved from WMI.Config.
Currently this is represented by a string. The Plug-In uses this initialization
data to choose where to obtain its objects from.
Riid - This is the interface requested by the GDI. Currently only
nD_IhugBatchCollection is requested, but future interfaces could be
requested here.
The interface returned by the plug-in is the following:
The AsyncProcessRequest interface is called if any data is destined to the
plug-in from the batch. The collection is passed:
A nmgServerltemCompletion interface. This interface supports a single
method - "Done" that is called when the request has been completely
processed. The caller can immediately return from AsyncProcessRequest.
The caller must call "Done" unless they fail the AsyncProcessRequest call.
Failing to call "Done" results in the client's Execute call not completing.
A Batch-Array, this interface provides access to the actions requested by the
client.
A start and end index for the batch array. This allows the router to create
one batch which is then shared by the various plug-ins. The plug-ins may
be executing in parallel if the client requests parallel execution semantics.
The batch array has the following interface:
The batch semantics, any associated transaction and the work flow can be
retrieved from the batch array. The interfaces are not returned with an increased
reference count since they are aliased to the batch array. The ItetnAt method
returns the batch action at a given index. This contains an enumeration giving the
type of item. It also contains fields that the plug-in can set to indicate whether the
item completed and any error information. It contains a union which contains the
parameters passed in by the caller for the corresponding action.
Interception
Now that the plug-in interface and router have been defined, a discussion of
the implementation of interception follows. Interception is implemented, in the
illustrated and described embodiment, as another plug-in interface to which the
batching router can send data. However, unlike the configuration information
stored for a normal plug-in, which only requires the collection id (or class for
queries) for routing, an intercepting plug-in specifies the following pieces of
information:
• The intercepting action. The interceptor can specify whether it wants to trap
Gets, Sets, Queries, Commands or Notification.
• The intercepting class. The interceptor specifies what class of object it
wants to be invoked for. The class can be specified as '*' in which can it
will be registered for all classes.
• The intercepting collection. The interceptor specifies the unique QUID of
the collection whose objects it wishes to intercept. This collection can be
specified as '*' in which case it will be registered for all collections.
• The intercepting object, The interceptor can specify the unique object id of
the object it wants to monitor (if it does this it should intercept the object's
Delete command to remove the interception). If '*' is specified, then the
interceptor is invoked for all classes.
Each registration in the interception table is assigned a GUID. The
interception mechanism will utilize the "Chain of command" pattern. The actions
taken by the batching router are as follows:
1. Before routing a given item in the batch to a collection, the router first
checks to see whether there is an interceptor registered for the call using the
fields described above.
2. If there is, a contiguous set of actions is found that are destined to the same
interceptor on the same rule.
3. The GUID associated with the interception registration is inserted onto the
work flow and the interceptor is invoked.
The interceptor at this point is passed the batch actions, like any other plugin
would be. It changes the execution of the action in one of two ways:
1. It calls RecurseBatchActionsQ against the router and passes in the work
flow again, plus the original batch actions. This does not result in any of the
actions being modified, but it allows the interceptor to read them and
perform additional actions itself (for instance, if it is a synchronizer, it will
use this to trigger a synchronization action if the cached data is stale).
2. It interprets each batch action and then modifies them by calling
RecurseDataBatch and passing in the work flow again. This option allows
the interceptor to change the outcome of an action, but it is (intentionally)
much more difficult to implement For example, to change the outcome of a
'Get", a temporary proxy object would have to be instantiated to hold the
result of the real objects get, and a new type map would have to be
registered to write to this temporary object.
Either of these calls will result in a call back into the batching router. The
batching router performs exactly the same lookup as before, except, it will not call
into any interceptor that has its GUID token listed in the work flow. Thus, each
interception in turn will be exhausted until the action is routed to the destination
object. This same mechanism prevents a well behaved set of interceptors and
objects from exhibiting recursion since the router will automatically ignore
interceptors that have already been called if the call becomes recursive.
Because the interceptor is responsible for chaining the call, it can choose to
execute actions before or after chaining. It can change the semantics of a batch.
Typically, if the batch isn't transactional, it might want to ensure that the
interception is transactional. Since it is invoked asynchronously, it could even
execute its interception in parallel with the intercepted action.
Uses of interception
Interception is useful for two purposes—system extension or aspect
extension. Interception can be used by IHVs to extend the behavior of a batch
action. For example, when a queue is paused, another action might need to be
taken by the driver. These extensions are reasonably safe as long as they do not
attempt to change the outcome of the original action. For this reason, the
RecurseBatchActions method is made much simpler to implement. Changing the
outcome of a batch action will be much more difficult since it requires interception
and modifying all of the parameters, including some sticky ones like type maps,
client objects and client object collections. Interceptions could also be conceivable
used as mechanism for maintaining compatibility and for patching systems. For
example, if a revision of the system results in a plug-in misbehaving, an
interceptor could be created that changes the calls into it to ones that it
understands. A system could, then, be serviced by supplying interceptors that, for
example, patch a security hole by intercepting a call with a string that is too large
before it reaches the target object. This could even be done quite generically, e.g.,
an interceptor could be written that rejects all strings that are larger than
MAX_PATH, or, a table of appropriate string lengths per field could be provided.
Where interception is most useful is in implementing an aspect of the
system that cross/cuts a large number of objects. For example, an interceptor could
be written that logs all of the actions in the system that follow a particular pattern
(for example, they fail with "Access Denied").
Object Commands
Object commands are different from object properties in that object
commands are only invoked on the server side. This is because the command
invocation is performed by passing an array of variants from the client to the
server and back. There is no notion of a client side proxy object that is invoked.
The data representation is shown just below.
As can be seen above, a server class description is the same as a client class
description, but also includes a description of a set of commands. A command has
a type, a command and a description. It contains a pointer to a command interface.
The command interface works analogously to an accessor interface used in
accessing properties. The command interface is passed the object to which it must
apply the command and an array of ImgVariant's that contain the input parameters
for the command. Every command can be asynchronously executed, which is why
the interface has both a BeginCommand and EndCommand method.
Each command also defines the type of all of its parameters and also has a
list of all the descriptions for each command.
Like property accessors, commands can be built on top of dynamic types.
However, since at a fundamental level they correspond to performing an action (as
opposed to retrieving data) and since dynamic proxy objects don't have to be
constructed for wire marshalling purposes when invoking commands this is not
likely to be as useful as for properties. It might have utility if a command were
extended via interception.
IATL Commands
IATL provides a mechanism to map GDI commands directly to C++ class
method invocations, exactly like it provides a mechanism to map CDI type map
properties directly to C++ fields and methods.
IATL allows the caller to implement the command as a single method in
which as the asynchronous BeginCommand, EndCommand pair is automatically
built for the implementer. This is useful if the implementer wants to implement a
command which does not do any IO. Alternately, IATL lets the class provide two
methods and implement the command asynchronously. The mechanism used to
huild IATL commands is summarized in the code excerpt just below.
This excerpt shows a. number of different ways that methods from a class
can be exposed as a GDI command.
The Print command is implemented in ServerPrinterCommandBase and is
implemented as a single synchronous method. All synchronous methods receive a
Img::iatl::CommandParams parameter as their first argument. This structure holds
the workflow and common data interface associated with the command. The
second parameter maps to the first command argument and so on. In order to
disambiguate which parameters are input and which are output parameters, the
caller must supply a parameter to the Command function to indicate how many
input and output functions the method has. For example, when the print command
—/
is referenced:
Command(glln, IMQ_ATTR_METHOD(ServerPrinter::Print), L"Print",
L"This prints out something"),
The glln parameter indicates that this is command that has one input
parameter. The input parameters must precede the output parameters in any
method that can be invoked as a command.
Just like a property type map, a command type map can be inherited from a
base type into a derived type. This is accomplished with the Inherits function:
Inherits (IMQ_ATTR_INHERIT_TABLE (ServerPrinterOommandBaBe)) ,
The types of the command parameters will automatically be deduced from
the method arguments. However, the optional description field must be supplied in
the table. This is done via the InDescrip and OutDescrip functions. For example:
Command (gllnlOut, IMG_ATTR_HETjflOD (ServerPrinter: iPrintAndRandom)) ,
InDeBaripdi" Something that will be output to the debugger"),
OutDeBcrip (Ii"A fractional random number"),
Here the Command Function provides the basic definition of the command,
and the InDescrip and OutDescrip functions describe the first input and parameters
respectively. If more that one parameter is described, multiple calls to InDescrip
and OutDescrip can be made.
The other noteworthy feature shown by the IATL command map is the
ability to implement an asynchronous command. This is illustrated by the
BeginNothing and EndNothing methods on the ServerPrinter class:
This is referenced by the following in the command description:
The Command function in this case does not need to be told which are
input and output parameters since input parameters are by definition taken by the
Begin method and the output parameters are by definition returned by the End
method, hi this case, the Begin method and End methods also take different
structures to a synchronous (single method) command, namely:
Img::iatl::InCommandParams and Img::iatl::OutCommandParams. The
InCommandParams contain the callback interface to indicate that the operation is
complete and the context information that can be saved and associated with the
operation. The End method is passed back the context information and can return
the result of the command as an error code or a set of return parameters.
Query compiler
The query compiler provides a mechanism to compile a GDI query into
either a filter that can be applied against any object that has a class description or a
parse tree. The interface exposed by the query compiler is shown in the code
excerpt just below.
A server side GDI instance provides the mechanisms to compile a query. If
a query filter is desired, then the CompileQuery method of the nmgCommonData
server interface is used. If a parse tree is required, then the CompileQueryTree
method is called.
The query filter is returned as a factory. When a particular class description
is passed to the query filter factory then a query filter is returned. The query filter
under the covers builds a type map against a class that is dynamically generated by
the query. When it is bound to the server object is builds an optimized map against
the server object. This optimized map is used to populate the query object against
which the query is then applied.
The query filter that is returned from the query filter factory is bound by
definition to a particular server class. It can be applied against multiple server
class object instances. It is not thread safe. If the caller wants to use the same
query from a different thread then, they can obtain a different query filter instance
from the same query filter factory and use that in a different thread.
A query filter has the advantage that it can be applied against any class that
supports a class description. However, for a filter to be applied against a class the
class must be instantiated and all instances that could potentially be matched by
the filter must be .evaluated. This is fine for a reasonably small set of objects
(possibly on the order of 10,000 for a modern computer), but it performs
extremely poorly for a large set of objects. For a large set of objects, a query
mechanism such as used by SQL should be used. However, the GDI query syntax
is not identical to the SQL syntax. To solve this problem, a query tree can instead
be compiled and then translated to a SQL query. Allowing the GDI query compiler
to run allows issues such as operator precedence and parentheses to be evaluated
by the GDI compiler consistently with how it would handle the same query for a
query filter.
The returned interface from the CompileQueryTree method returns an alias
to a compiled query tree through the Root method. The memory occupied by the
returned parse tree will be returned to the system when the IlmgQueryTree
interface is released.
hi one embodiment, a parse tree starts with a query node; the query node
can either be a comparison, or it could be a Boolean node. If it is a Boolean node,
then the node has an operator and in turn consists of two query nodes. This is the
construct that allows a parse tree to be built. If it is a comparison node, then the
comparison consists of a parse identifier and a constant. If the identifier has
appeared twice in the same expression, as would be found in the following query:
"Printer.cJobs > 10 AND Printer.cJobs instance will appear twice in the parse tree. The pCookie field is a placeholder in
the tree that allows a caller to store their notion of an identifier in the cookie. For
example, if Printer, cJobs translated to the NuniberOfJobs column in a database,
this could be stored in the cookie. Aside from providing an extensibility point, the
cookie is ignored.
Transactions
Transactions are the mechanism that the GDI uses to ensure that an
operation either succeeds completely, and that if it fails, no intermediate state is
stored on the machine. The actual mechanism to handle transactions is distributed
between the GDI and the CAS. The GDI provides the interfaces to handle
transactions and work flows. The CAS provides the mechanisms required to
ensure transactional correctness and to provide for deadlock-free locking
strategies.
Each transaction is controlled by precisely one transaction object. Each
transaction object has one logical store object. Each logical store in the system is
referenced by a QUID. The store object coordinates handling any persistent state
that the transaction needs to coordinate. The restriction of only one store is
reasonable if one considers that the system cannot guarantee that two commits to
two transactional stores can be guaranteed to succeed if the stores are not
distributed resource managers (DRMs). In the case that they are DRMs, then the
transaction will be coordinately across one logical store, namely that provided by a
distributed transaction coordinator (DTC), like that found in COM+.
Each transaction also holds a number of dependent actions. The rule is that
a dependent action cannot fail to either Commit or Revert its operations. The
dependent actions are generally used to couple cached state (or locks) to the state
of the persistent store. For example, the CAS clones each object before applying
the transaction to the store, if the store successfully commits the changes, then the
cloned objects are swapped into the cache. The transaction interfaces are shown
just below.
Note that all of the interfaces use the COM asynchronous calling pattern if
the caller or the implementer desires to use an asynchronous calling mechanism. If
they do not, they can still make synchronous calls to the interface or implement
synchronous transaction dependent actions.
Anyone wishing either to implement a store or a transaction dependent item
must implement the IlmgTransactionDependentltem, interface. This interface has
two methods, Commit or Revert. A store may fail a commit (but not a revert) any
other dependent item must always succeed its Commit and Revert methods.
Each transaction can have precisely one store. The current store and its
store GUID can be retrieved with the GetTransactionalCollection method.
If the caller wants to set a transactional store, the
SetTransactionalCollection call is used. Since transactions are a multi-threaded
interface, the SetTransactionalCollection takes both a GUID, the collection to be
set, and it returns the existing collection, if it is there. There are three cases (as
indicated by the ETransactionSetResult return parameter).
• The transaction currently does not have an associated store object. In this
case, the return will be TransactionCollectionSet and the passed in
collection will become the store associated with the transaction.
• The transaction currently has a store object, but it has the same GUID as
the one you are specifying. In this case, the transaction object will return
the existing collection to the caller. The caller should release their previous
collection and continue the call with the persistent store currently hi the
transaction.
• The transaction currently has a store object, and it is a different store (it has
a different store GUJD). In this case, the transaction object will return
TransactionCoUectionlncompatible. The caller will typically fail the
operation at this point. (This will cause the transaction to revert).
Since the transaction coordinator cannot know the interface to the store
object (it could be anything from a transactional file system to a SQL database),
this is returned to the caller as an lUnknown from which they can retrieve the real
store interface via Querylnterface. The store must implement the
IlmgTransactionDependentltem so that the transaction object can correctly
Commit or Revert the changes to the store.
Transaction dependent items are committed or reverted in two phases. This
is specified via the TransactionOrderFirst and TransactionOrderLast parameter to
InsertDependentAction. Transactions that rely on the CAS for locking should only
use TransactionOrderFirst since the CAS uses dependent items of
TransactionOrderLast to ensure that object locks are released after the
coordination transaction is committed or reverted.
Work Flow
The GDI interfaces are largely asynchronous. Thus, a single operation can
be completed on one thread, or it could be completed on multiple threads. Systems
that use less asynchronous programming often can associate state with a thread,
for example, a thread could have an associated transaction that is always implied
on any methods issues from the thread once the transaction is started. The notion
of a work flow replaces this general concept of thread in the GDI. A work-flow
always corresponds precisely to a batch that is submitted by the user. The user
does not create a work flow. It is created automatically by the GDI when a new
batch is issued. A server plug in can-create a work flow from scratch if it wants to
for its own purposes.
A work flow also provides support for creating new asynchronous work
items and associating it with the work flow and hence the origination batch. When
the batch is cancelled, the work flow is cancelled and hence, all items on the work
flow are requested to cancel. Using this mechanism results in the GDI not having
to support an explicit cancellation method on every interface, the method just
receives a work flow instead.
The work flow interface is shown just below.
We will focus the discussion on the following services provided by the
work flow: work items, tokens, scopes and transactions. Transactions are covered
independently above, but the work flow has some special handling for them.
Work Items
In order to implement a work item, the caller must implement the
IlmgWorkltem interface. The Run method is called on the interface when the
work item executes and the Cancel method is called if the work flow is canceled.
There are three basic types of work item:
A normal work item, created with "AddWorkltem". This sort of work item
will run as soon as sufficient resources are available. Like all work items,
the caller can specify what sort of work item is running through the work
item flags.
A wait-able work item, created with "AddWaitableltem". A wait-able work
item will run when its associated event is signaled.
A dependent work item, created with "AddDependentltem". A dependent
work item isn't run, but its "Cancel" method is called if the work flow is
cancelled. This allows the caller to use a different asynchronous method
(for example, they might call ReadFile with an overlapped structure), but
still receive cancellation notifications from the original work flow.
The IlmgWorkltemControl interface fulfills two purposes - it allows a waitable
work item to return an event handle that can be set to trigger the work item. It
also allows the caller to cancel a particular work item by releasing the interface.
The cancel call in this case is always asynchronous, i.e. the work item is informed
of cancellation but the Release call on the interface does not wait for the
cancellation to complete.
This is unlike the behavior of the Shutdown method on a work flow. The
shutdown method synchronously waits for all of the work items in the work flow
to be cancelled.
Tokens
Tokens are used for marking particular state on the work flow. They are
intended to be used to prevent infinite recursion when implementing interception.
Namely, a particular type of interception can add a token to the work flow and
then prevent that sort of interception from occurring again.
A token is a GUID - tokens can be added to a work flow, found in a work
flow and removed from a work flow.
Transactions
A work flow can have precisely one transaction. The work flow is the only
interface" from which transactions can be created. The work flow provides a
number of helper methods for dealing with the current transaction.
• CreateTransaction creates a transaction and associates it with the current
work flow. If the transaction already exists, this is considered benign, the
current transaction is returned. (The caller can deduce that this occurred
through the ElmgBatchTransactionCreation return).
GetBatchTransaction returns the batch transaction currently associated with
the work flow, if it exists. It returns NULL if it does not.
InsertTransactionalCollection inserts a collection into the transaction
associated with the work flow. The inserted collection or the existing
collection is returned via the ppISetTransCollection returned. If the
collections are incompatible (they use different GUIDs), the call will fail.
GerTransactionalCollection retrieves the transactional collection associated
with the transaction associated with the work flow.
Scoped work flows
A work flow can contain other work flows. When a work flow is scoped
within another work-flow, it automatically inherits all work items and tokens from
its parent. It also inherits any transaction that is in progress from the parent work
flow. Scoping work-flows allows two main types of functionality:
Tokens in parent work flows cannot be deleted. This allows the set of
tokens in a work-flow to be "locked" until the work flow invocation
recurses back out to the caller. This is useful in that it can prevent an
interceptor from inadvertently removing tokens that it shouldn't.
A non-transactional request from a user might require a transaction in order
to be executed as an implementation detail. For example, a deletion request
might require a number of sub-objects to also be deleted. Since the original
batch might contain other items that should not or can not be made
transactional, we wouldn't want to add a transaction to the work flow
created by the caller. The solution is to produce a work flow scoped inside
the first work flow and then associate the transaction with the inner work
flow. This allows any cancellation requests on any work-items to be
maintained and also allows any tokens on the original work flow to be
preserved without making the original work flow transactional.
Server Side Notifications
The GDI supplies support for plug in collections wishing to support
notifications. This allows a notification channel to retrieve data from a remote
collection and a local (in-proc or sandboxed) collection at the same time and still
consistently handle the notification channel on behalf of the client. The CAS
provides additional support for notifications on particular objects (such as ensuring
that notifications are correctly serialized through its locking infrastructure).
Each batch collection is given a IlmgNotificationDataServer interface that
is used to push data through to the client collection for each notification channel.
The GDI itself maintains the client collection and other infrastructure for the
notifications.
The notification data server interface exposes three methods:
RegisterShutdown - This allows the caller to register an interest in the
channel being shut down. For example, a remote access adapter might need
to know to pull down its own notification channel when the client
notification channel is pulled down.
CreateNotificationObject - This create a new notification interface that
must be used to push data through the notification channel. Each
nnagPushNotifyDataThroughChannel instance is not thread safe. The
system provides the guarantee that any changes sent through an
IlmgPushNotiryDataThroughChannel instance are sent as a group or not af
all.
SendFailure - It is possible that a fatal error occurs that prevents
notifications being sent through the channel. In this case, the SendFailure
method can be called. It is guaranteed to tear down the channel. It will also
send the error information to the caller in a best effort manner.
The push notify data through channel interface has the following methods:
• SendAddObj ectNotify - This tells the channel that a new notification obj ect
has been added. The object could either have been added because the object
really has just been created, or it might instead have changed state to match
a query filter.
• SendRemoveObjectNotify - This tell the channel that either an object has
really been deleted, or that it no longer matches fhe query filter and hence
has logically been removed from the client collection.
• SendChangeDataNotify - This sends individual field infbnnation down the
channel to the client. The map index is the index of the field in the client's
registered class description. Each change sent can have one of two
behaviors. If the change is buffered somewhere in the system and another
change to the same field occurs, then the new field value takes its place.
This has the advantage of minimizing the storage required for any buffered
notification data. Alternately, every change might be significant, in which
case the caller can request that the history of every change is kept.
• SendDone - This finishes the changes the have been sent to the
nmgPushNotifyData interface. Either the initial data for the notification is
being sent, in which case NotifyUpdatePopulate variation can be used, or
this is a notification about a subsequent change in object state in which case
NotifyUpdate can be specified.
Friendly name resolution plug-ins
The mechanism that a plug-in batch collection uses to plug into the
canonical name-space is described in section entitled "Plug-In Interface" above.
Basically, each collection is identified by a QUID. If the batch collection also
wants to support friendly names it must register a friendly name resolution
handler. This translates a friendly name of the requested class to the canonical
name if possible. Friendly name resolvers are registered with the GDI in the same
way that normal plug-ins are registered, except that the friendly name class is used
instead of a collection GUID and a different class factory and interface are used
instead. The class factory and plug in interface are shown just below.
When the client requests a name resolution, if the friendly name resolver
cannot find the name in the cache, the name resolving handler for each registeredplug-
in for that class is called (in a parallel invocation). Each name resolver
attempts to resolve the name, when the name resolution is finished, the resolver
calls Done on the IlmgServerltemCompletion interface. The name resolver will
then call EndResolveName with the same context returned by BeginResolvaName.
The name resolver then returns an IlmgCanonicalName iterator that contains any
canonical names that the name resolver has determined to correspond to the
friendly name.
Note that the name resolver plug-in will typically perform its name
resolution by using Gets, Queries or Commands against the. GDI interface. Thus,
implementing an asynchronous interface will typically not be too problematic
since it will in turn be calling another asynchronous interface.
In order to prevent the caller from having to implement an
IlmgCanonicalName iterator for every query collection, a collection interface is
provided that accumulates canonical names and from which an iterator instance
can be returned. This interface is shown just below.
Collection Adapter Service
In the illustrated and described embodiment, the nmgBatchCollection
interface is deceptively complex to implement. The caller must run through each
batch item, decode it, and decide how to respond to it. Generally, the only reason
why a collection would want access to the batch is to preserve the batch for
transmission across machine or process boundaries. In the case where the batch
actually interacts with a group of objects that are loaded to and from a persistent
store, this is a great deal of complexity for the plug-in to handle. For this reason
the Collection Adapter Service is provided. The Collection Adapter Service
provides the following functionality:
• It retrieves objects from a set of plug-in collections.
• It caches instantiated objects and flushes them when not in use.
• It manages the "binding of the client objects to the server objects through the
type map.
• It coordinates transactions between compatible collections.
• It manages object locking in conjunction with transactions.
• It manages both synchronous and asynchronous objects.
• It interacts with the query compiler to implement the object.filters.
• It makes best use of resources by dynamically choosing to execute calls in
serial or in parallel.
• It maintains object concurrency through an optimistic locking scheme.
• It handles object change notifications.
If the basic GDI is the glue that holds the various plug-ins together, then the
Collection Adapter Service is responsible for ensuring that a smart, asynchronous,
high performance plug-in is as simple to implement as possible. The collection
adapter service model is shown in Fig. 14 in accordance with one embodiment.
A given CAS instance can have multiple object collections plugged into it.
Each object collection supports a single class name, for example, "Printers" and all
the objects in a given object collection are homogenous. Thus, they can be
described by a single class description which expresses all of the properties and
commands that a given server object implements. The collection adapter service
uses the server class description to transfer data between the client and the server
objects and execute object commands. Each object in the server implements some
basic functionality via its HmgObject interface. The HmgCollectionAdapter has
the following interface:
The client instantiates a collection adapter by calling "CoCreatelnstance" in
its IImgBatchCollectionFactory::CreateInstance method. It calls
HmgCollectionAdapter:.'Initialize and passes it the IlmgCornmonDataServer
interface that it was passed. It then instantiates and registers each of its object
collections through the RegisterObjectCollection call.
An nmgObjectCollection has the following methods:
The GetTransactionCollectionData call returns how the collection supports
transactions, a collection can support no transactions, transactions as dependent
item (this can be supported by transient collections) or it can support transactions
by returning a custom transaction interface that depends on the underlying storage
system and an ID that uniquely identifies the scope over which a transaction can
be successful.
The GetCollectionData call returns the object class name, the Objectld used
in the optimized type map binding and the server class description that describes
each object. The remaining calls, BeginGet and EndGet and
BeginEnum/EndEnum allow objects to be retrieved from the collection. The
shutdown method is called by the CAS when the IImgBatchColIection::Shutdown
method is called.
Any call that the CAS executes that is likely to be time consuming will take
the form of a Begin/End pair. Each Begin call takes an nhigServerltemCompletion
interface and allows a context to be returned through the ppContext parameter.
The CAS provides the following guarantees to the implernenter of the interface:
• If the Begin call fails, the End call will not be called.
• If the Begin call succeeds, the End call is guaranteed to be called.
• The End call will not be called until the HmgServerItemCompletion::Done
method is called.
• The End call will not be made until the Begin method returns.
• The End call will be passed the context returned in the Begin call.
These guarantees allow the Begin method to implement a synchronous call
by calling Done within the Begin method. Any IO bound operation it performs
should be executed either in another thread (preferably consolidated with other
operations), or it should be implemented in turn as an asynchronous call. When the
asynchronous item completes, then the nmgServerItemCompletion::Done method
should be called. If the begin method needs to keep track of any state specific to
the call, then it can be returned in the ppContext parameter. Since the CAS
guarantees that the End call Vill be made, the context can always be freed. The
CAS uses the following mechanism for scheduling calls:
If the call is synchronous (Done is called inside the Begin method), and the
caller requests parallel execution, the execution of the method is assumed to
be CPU bound. Thus, the next call will be serialized on the first. If a call
fails, the result is recorded, but all subsequent calls are still made.
If the call is asynchronous and the client request parallel execution, then the
next "Begin" method is called immediately in the origination thread.
If the call is asynchronous and the client requests sequential or transactional
semantics, then the remainder of the work will be performed in the same
thread as the HmgSeriverItemCompletion::Done method is called on.
These rules mean that a client collection or object of the CAS must ensure
that IO bound operations are executed in the asynchronous calling pattern,
otherwise it can prevent parallel execution of other work items.
Objects have the following interface:
The methods to be implemented are as follows:
Initialize call is called by the CAS when the object is first returned to it
from a collection. It passes the object a handler interface that allows the
object to delete itself, lock itself, interact with transactions and send change
notifications to the rest of the system. It can also return a call filter. The call
filter indicates whether the object supports partial instantiation (in which
case the BeginGet/EndGet methods will be called on it before reading any
data from it). It also indicates whether it supports persisting the object by
only writing out certain fields. In this case the BeginSet/EndSet methods
will be called specifying precisely which properties the caller specified in
the set.
GetRealObjectAddress returns the object address that the
ImgServerClassDescription accessors are relative to. This is necessary
because if the object is implemented using multiple inheritance the
IlmgObject interface address is not necessarily the same as the real object
address.
BeginGet/EndGet — These methods are only called if the object indicates it
supports a partial object in its call filter. The object will be passed the tags
that indicate which fields the optimized map is about to read. The object
can use this to fetch heavy fields after its first instantiation from a persistent
store. If the object does this and the call is IO bound (which it almost
invariably will be), then it must use an asynchronous calling pattern.
CloneObject — The CAS assumes that the object is implemented in an
immutable fashion. This allows the object to remain unlocked while data is
being read from it. It simplifies transactions since the object can be held in
its duplicated state until the transaction commits. Thus, the object will be
asked to Clone itself before a set occurs. Object must be coded in an
immutable fashion to be compatible with the CAS.
BeginSet/EndSet - This pair of methods is called in a set after the object
has been cloned and its accessors have been used to change its state. The
object can validate that its new state is valid in this call. If it requests it in
the call filter, it will be told which fields were specified by the client when
the set occurred.
Object Handler
The object handler is the interface that is passed to each object by the CAS
to allow the object to interact with the CAS in various ways. There is precisely one
instance of an object handler for each object that is held cached in the CAS. The
IJmgObjectHandler also exposes an asynchronous lock for each object. This lock
is used by the CAS to logically serialize access to the object for state changes
which includes both transactions and change notifications. Several actions on the
object require the object to be locked. The object handler interface enforces this
semantic. The object handler interface is shown just below.
Object Asynchronous Lock
An important method is GetObjectLock, which allows the caller to retrieve
the lock associated with the object. The lock returned is an asynchronous lock with
support for transactional locking. The lock and the associated semantics are used
by the CAS and the same semantics should be used by other callers. The interfaces
are shown just below.
The lock is asynchronous, i.e. it calls you back when you can acquire the
lock. This is an intentional design choice that provides for the following
capabilities:
• A work flow attempting to acquire a lock can be cancelled. Since the lock
interface is asynchronous, the caller can be notified that the lock acquisition
attempt was cancelled.
• Since locks are used for serializing object access across transactions and
since transactions might have to acquire many objects and will have to
typically write the state to a store, an object lock could potentially be held
for a long period of time. Thus, any lock access could potentially be
waiting on an operation which is IO bound. Using an asynchronous lock
allows the thread to be reused for other CPU bound operations, rather than
having to wait for the IO to complete.
Note that the CAS does not use the object lock for Gets/Queries and
Commands. This means that although a transaction might be logically holding an
operation, it only serializes sets and notifications. If the caller wishes a command
to be transactional, they must create the transaction themselves and acquire the
object lock.
A caller wishing to acquire the lock must implement the HjngLockEntry
interface. This interface has two methods, EnterLock and Notify Cancel.
EnterLock is called when the caller has acquired the lock. An
ImgLockContext is passed • to the EnterLock function. This lock context is
precisely associated with that particular acquisition of the lock and must be handed
to the lock interface when the lock is released. This fulfills two purposes:
• If another caller erroneously tries to release the lock without acquiring it,
the release has no effect.
• The fact that a lock must be held can be expressed semantically by other
interfaces by requiring the lock context to be presented. This can also check
that the lock is indeed held before doing the associated action.
In addition, if the workflow on which the caller acquired the lock is
cancelled, then the NotifyCancel method on their interface will be called. They
can then decide whether they still must acquire the lock, or whether to abort their
access to the lock. They can cancel their acquisition of the lock by releasing the
returned lock sequence interface on which they called AcquireLock.
It would be undesirable if the caller couldn't guarantee that a lock could be
acquired. For example, they might wish to acquire a lock, increment a reference
count and then release the lock. Then they might perform some actions on the
object, acquire the lock again and decrement a reference count. If they couldn't
acquire the lock for the second operation, then the reference count would never be
correctly decremented.
The ability for the caller to guarantee access to the lock is provided by the
HmgLockSequence interface. Once a lock sequence is returned from the lock any
sequential acquisitions and releases of the lock are guaranteed to succeed. This
works because as an implementation detail, the HmgLockSequence interface is
implemented by an object that reserves enough storage to always be able to
acquire the lock.
Locks and Transactions
A transaction might need to lock many objects in sequence. This provides
an opportunity for the system to deadlock if there is any cyclical acquisition of
locks between more than one transaction. It is semantically illogical for two
transactions to be holding the same lock, so the lock provides special support for
transactions. This is automatically utilized by the CAS when locking the object
before setting properties and it should be used by any objects that wish to
implement transactional semantics.
A lock can be held by precisely one transaction at a time. A transaction
wishing to acquire a lock uses the AddTransactionToLock call. If there is already
a transaction holding the lock, the call will return FALSE in the
pbTransactionAdded parameter. The transaction should then be reverted (which
frees any locks or resources currently held by the transaction) and wait for the
existing transaction to complete before returning a failure to the caller. The caller
calls WaitForTransactionToFinish to wait for any transaction holding the lock to
complete (they will be notified asynchronously). If there is no transaction holding
the lock, they will be called back immediately. The lock will automatically be
disassociated with the transaction if the transaction is committed or reverted.
The reason that the caller should wait for the existing transaction to finish is
to guarantee that if the caller retries the operation they will not simply retrieve the
old (unchanged) object state again and "spin" against the transaction holding the
lock.
CDI/CAS optimistic locking model
The GDI intentionally does not expose any locking constructs to clients.
There are a number of reasons for this:
• Each lock would have to add additional context overhead to the server that
could be maintained by a client.
• If a client went into an error state and held onto a lock, it could cripple the
operation of the server.
• In order to handle clients not releasing locks, a manual lock-breaking
mechanism would have to be added to the server. This is additional UI and
maintenance that is otherwise simply avoided.
However, it is desirable that the server state is maintained correctly and that
the "dueling administrators" problem is avoided. If two administrators
simultaneously tried to rename a printer, we would want to ensure that one rename
succeeded and the other failed.
The system maintains these semantics partly by the mechanism that
transactions use to acquire object locks. It is also maintained by giving every
object an "ObjectLockld" property. Simply put, in order to change an object state,
you must also supply a lock id that matches the current object lock id. This
indicates that you were aware of the last object state before attempting to change
the object. If you supply an object id that does not match, the attempt to change
state will fail. In response the caller must re-get the properties that they wish to
change and hence re-get the lock id. A well written client would then check the
properties to ensure that the change they wish to effect is still meaningful and then
attempt the operation again.
Object change handler
In order to retrieve a change handler for an object, the object lock must be
acquired and then the GetChangeHander method on the object handler must be
called. The GetChangeHandler method requires the lock context. The other
mechanism that an object can use to acquire a change handler is that it is passed in
to the property accessor as a parameter during a set. (The CAS in this case
acquires the object lock before setting any object properties for you). The object
change handler interface is shown just below.
The GDI change handler interface is discussed in section above. That
interface provides the capability to send changes down a particular channel. The
CAS adds the capability to correctly scope queries and handle all of the channels
that might be interested in a particular object. All that the object has to do is
indicate which properties have changed. The simplest mechanism is to call
NotifyChange and pass the tag for the field which has changed. The CAS will then
retrieve the data for that field via the property accessor and send the property
change notification to all channels that are interested in the change.
In order the save the CAS the effort of retrieving the properly, the caller can
also directly specify the data via the NotifyChangeData call.
Finally when all of the changes have been accumulated into the change
handler, they can be sent via the SendChanges method. If any of the changes
cannot be sent, the CAS will tear down all notification channels that might target
that object.
IATL support for change notifications
Change notifications are not difficult for an object to implement. To make
it even easier, if a property of your object is only modified by through a GDI
property get or set and that property is implemented as a data member, then IATL
will automatically generate the change notification code for you. When specifying
the field, the gNotify modifier need only be passed to the Field function in the type
map. This is shown just below.
This will automatically build a get and set accessor for the property. If the
property changes, the change will be sent to the change handler supplied to the
property accessor by the CAS.
Remaining Object handler functions
Delete Object
This function deletes the object from the CAS's cache. This will also send a
change notification to the clients. The object must be locked and the lock context
presented in order to delete the object.
Current Object
The CAS uses an immutable object model. When an object is modified, it is
cloned and then inserted into the cache if its state is changed. If an error occurs,
the new object is discarded. An interesting implication of this design is that if the
object wishes to send change notifications as a result of an asynchronous event or,
if it otherwise needs the latest version of the object during a property get or during
a command, it cannot know that the instance of the object it currently holds is the
correct one. To handle this case, the object handler can retrieve the current object
instance. Since object state changes are protected by the object lock, the lock must
be acquired before the current object can be meaningfully retrieved.
HoldObjectlnCache and ReleaseObjectFomCache
The CAS normally caches an object in memory for about ten seconds
before it is discarded. An object might decide instead to remain permanently in the
cache or to remain cached while another object is cached. To handle this
requirement, the HoldObjectlnCache method can be called on the object handler.
When called, the object will be held in the cache until the corresponding
ReleaseObjectFromCache call is made. HoldObjectlnCache can be called any
number of times the object will only be released when the same number of calls to
ReleaseObjectFromCache is made.
You can only safely call HoldObjectlnCache in cases where the object is
actively held in the cache already because of some other operations. Otherwise,
there is a potential race condition where the object is being released from the
cache while the call to HoldObjectlnCache is being made. This will not result in a
crash, but, obviously, the HoldObjectlnCache call cannot be honored in this case.
The points at which HoldObjectlnCache are guaranteed to succeed are:
• During the Initialize call to the HmgObject interface supported by the
object.
• During any property get or set or command invocation.
• When the caller otherwise knows that they have issued a
HoldObjectlnCache call in the past.
Framework Provided Collections
The nmgObjectCollection and EmgObject interfaces are not particularly
difficult to implement, and there will be cases when a plug-in using the CAS will
want to or need to implement both. For example, when writing an adaptor that
represents objects on a down-level server via the new data model, you will want to
provide your own HtngObjectCollection. However, there are many cases where
standard framework provided object collections can be used. These are shown in
Fig. 15.
The framework provided collections, and their function are as follows.
The/n Memory Collection provides a dynamic collection of objects that are
not persisted. This collection can be useful when supplied by plug-ins with a few
real immutable objects to expose (such as filter factories). It could also be useful
in providing a store for light-weight non-persist-able objects in such scenarios as
TS printing.
The Persistent Object Collection Service is the collection that provides the
ability to persist objects in a persistent store. There might be many stores in which
objects can be persisted including the registry, SQL databases or WinFS. Each
store will have its own Persistent Object Collection Service instance. The
persistent object collection service will provide support for the persistence of the
object state into the appropriate store. It will use the server type map
(ImgServerClassDescription) to retrieve the necessary object state for storage. In
addition, it will where it is able to, map queries directly into the query support
provided by the underlying store.
The Notification Shim Collection works as follows. In many cases the
system will be bridging to down-level systems that provide limited or no support
for notifications. In this case, the caller needs to enumerate all of the objects,
check to see when a new object arrives or leaves and fire the appropriate
notifications if any fields change. Because the IlmgObjectCollection interface is
public and the ImgServerClassDescription allows the caller to access all of the
fields on an object, a generic shim object collection will be supplied that
automates this behavior. This could also be used for callers that just don't want to
add the couple of extra lines of code to support notifications. However, a default
implementation can be provided for fields that will automatically generate
notifications when they change.
In summary, the framework will supply a number of canned object
collections that will result in most people never having to implement their own
collection interface. An adapter collection interface is the likely exception. A shim
collection is provided to automate the generation of events from a down-level
adapter.
IATL Implementation of DjngObj ect
IlmgObject is also not a particularly difficult interface to implement. In
order to make it as simple as possible to write a standard object, IATL provides a
standard implementation of IlmgObject that supports non-partial object
implementations. The classes it implements are as follows:
TServerObjectBase - Provides a default implementation of lUnknown,
Initialize, BegiuGet/EndGet and BeginSet/EndSet. The get and set
functions don't do anything.
TServerObject - This template adds the GetRealObjectAddress
implementation through base class templatization.
TServerDefaultCloneableObject - This template addes the CloneObject
method. The caller must supply a copy constructor in their derived class
and must either not throw exceptions, throw exceptions if in error, or return
an error from their IsValidQ method after the object has been copied.
TIMCServerObject - This object implements the BeginSet/EndSet method
pair for the In Memory Collection.
Managed Objects
IATL is designed to supply automated support for unmanaged C++ classes
by providing a template library that allows methods and fields to be expressed as
properties and allows methods to be expressed as commands through the GDI.
There are many advantages to remaining in the unmanaged space, however, the
system will also want to provide data driven support for managed objects,
especially as managed code improves in performance, robustness and design
stability.
Since the CAS encapsulates a lot of important functionality, it would be illadvised
to have a parallel managed implementation that does the same thing. The
solution is to use managed metadata to create an ImgServerClassDescription in the
unmanaged space and use it to populate and maintain a shadow unmanaged object.
The solution, in accordance with one embodiment, is shown in Fig. 16.
Here, the class meta-data from the managed object is mapped to an
ImgServerClassDescription in the unmanaged space. This class description uses
accessors that can manipulate a shadow unmanaged object. Each property in the
unmanaged object can be indexed and will correspond to a property in the
managed object. The managed object will use a change notification mechanism to
propagate asynchronous changes to the shadow unmanaged object. Whenever a set
occurs on the unmanaged object, the properties will first be applied to the
managed object and then the state of the managed object will be replicated back to
the unmanaged object. Commands with be directly mapped to method calls on the
managed object.
The advantage of this mechanism is that the most common operations on an
object, Gets and Queries, will be executed entirely on the unmanaged shadow
object. In addition, the unmanaged shadow object can be stored by the unmanaged
Persistent Object Collection Service and can be placed in the In Memory
Collection. This also bypasses the slow reflection mechanisms that would
otherwise be needed to perform a property get. Changes to the managed object can
be constrained to one interop thunk per batch action. Since this occurs after the
CAS, we cannot marshal the entire batch over before it is interpreted. This
limitation should be offset by avoiding the managed path at all in the query and
get cases.
Exemplary Client Device/Print Server Components
Fig. 17 shows an exemplary computing device having components that can
be employed in both a client device and a print system to implement the
embodiments described above.
Computing device 1742 comprises one or more processors or processing
units 1744, a system memory 1746, and a bus 1748 that couples various system
components including the system memory 1746 to processors 1744. The bus 1748
represents one or more of any of several types of bus structures, including a
memory bus or memory controller, a peripheral bus, an accelerated graphics port,
and a processor or local bus using any of a variety of bus architectures. The
system memory 1746 comprises read only memory (ROM) 1750 and random
access memory (RAM) 1752. A basic input/output system (BIOS) 1754,
containing the basic routines that help to transfer information between elements
within computing device 1742S such as during start-up, is stored in ROM 1750.
Computing device 1742 can further comprise a hard disk drive 1756 for
reading from and writing to a hard disk (not shown), a magnetic disk drive 1758
for reading from and writing to a removable magnetic disk 1760, and an optical
disk drive 1762 for reading from or writing to a removable optical disk 1764 such
as a CD ROM or other optical media. The hard disk drive 1756, magnetic disk
drive 1758, and optical disk drive 1762 are connected to the bus 1748 by an SCSI
interface 1766 or some other appropriate interface. The drives and their associated
computer-readable media provide nonvolatile storage of computer-readable
instructions, data structures, program modules and other data for computer 1742.
Although the exemplary environment described herein employs a hard disk, a
removable magnetic disk 1760 and a removable optical disk 1764, it should be
appreciated by those skilled in the art that other types of computer-readable media
which can store data that is accessible by a computer, such as magnetic cassettes,
flash memory cards, digital video disks, random access memories (RAMs), read
only memories (ROMs), and the like, may also be used in the exemplary operating
environment.
A number of program modules may be stored on the hard disk 1756,
magnetic disk 1760, optical disk 1764, ROM 1750, or RAM 1752, including an
operating system 1770, one or more application programs 1772 (such as a user
agent or browser), other program modules 1774, and program data 1776. A user
may enter commands and information into computer 1742 through input devices
such as a keyboard 1778 and a pointing device 1780. Other input devices (not
shown) may comprise a microphone, joystick, game pad, satellite dish, scanner, or
the like. These and other input devices are connected to the processing unit 1744
through an interface 1782 that is coupled to the bus 1748. A monitor 1784 or other
type of display device is also connected to the bus 1748 via an interface, such as a
video adapter 1786. In addition to the monitor, personal computers typically
comprise other peripheral output devices (not shown) such as speakers and
printers.
144
Computer 1742 commonly operates in a networked environment using
logical connections to one or more remote computers, such as a print server 1788
which, in turn, is connected to one or more printers. The print server 1788 may be
another personal computer, a server, a router, a network PC, a peer device or other
common network node, and typically comprises many or all of the elements
described above relative to computer 1742. The logical connections depicted in
Fig. 17 comprise a local area network (LAN) 1790 and a wide area network
(WAN) 1792. Such networking environments are commonplace in offices,
enterprise-wide computer networks, intranets, and the Internet.
When used in a LAN networking environment, computer 1742 is connected
to the local network through a network interface or adapter 1794. When used in a
WAN networking environment, computer 1742 typically comprises a modem 1796
or other means for establishing communications over the wide area network 1792,
such as the Internet. The modem 1796, which may be internal or external, is
connected to the bus 1748 via a serial port interface 1768. In a networked
environment, program modules depicted relative to the personal computer 1742, or
portions thereof, may be stored in the remote memory storage device. It will be
appreciated that the network connections shown are exemplary and other means of
establishing a communications link between the computers may be used.
Generally, the data processors of computer 1742 are programmed by means
of instructions stored at different times in the various computer-readable storage
media of Hie computer. Programs and operating systems are typically distributed,
for example, on floppy disks or CD-ROMs. From there, they are installed or
loaded into the secondary memory of a computer. At execution, they are loaded at
least partially into the computer's primary electronic memory. The system
described herein comprises these and other various types of computer-readable
storage media when such media contain instructions or programs for implementing
the blocks described, in conjunction with a microprocessor or other data processor.
The system described can also comprise the computer itself when programmed
according to the methods and techniques described herein.
For purposes of illustration, programs and other executable program
components such as the operating system are illustrated herein as discrete blocks,
although it is recognized that such programs and components reside at various
times in different storage components of the computer, and are executed by the
data processor(s) of the computer.
Conclusion
The various embodiments described above provide a pluggable architecture
that can allow third party component writers to insert new classes easily into the
system. A routing system is provided that allows data to be retrieved from
multiple data providers. In addition, a name resolution pipeline resolves human
supplied names to internal canonical names. Further, the various embodiments
provide the ability for a client to precisely specify the data that it wants to retrieve
from an object. An extremely efficient mechanism for retrieving data from an
object uses optimized type maps. Once the type map has been built, no further
string comparisons or searches need to be performed. A single pluggable interface
is also provided that that can support any data. This means that as far as setup is
concerned, there need be only one type of installable object. Other object types
can be obtained from a factory object through the collection. This can be used, for
example, to build the pipeline elements.
In addition, a set of services is provided that can allow any object in the
system to easily support queries, support notifications—including filtered
notifications, support caching and work scheduling.
The described embodiments can also provide the ability to tunnel over any
protocol or transport that can handle a set of fundamental types through the access
Adapters. Various embodiments also support the ability to provide collections of
objects that are transported over down4evel protocols, and to allow down-level
(and up-level) protocols to be dynamically added to the system.
In addition, an asynchronous data interface is provided. This is important
because synchronous interfaces choke the server whenever a large number of
ultimately IO bound data writes occur. It also simplifies UI programming since a
single UI thread can execute and not stall against the operations it is performing.
In addition, a batching interface allows arbitrary grouping of object
commands, gets, sets, queries and notification requests. This is important because
it enables clients to support operations such as deleting a collection of printers. It
is also advantageous in that it allows the effects of network latency to be reduced.
For example, when the UI wants to retrieve a number of properties about a
particular queue, it can batch all of its requests in one message which results in
one network round trip, rather than the many network round trips that are required
if the data is retrieved sequentially.
Further, the various embodiments can provide an almost completely
stateless interface, with the exception of notifications.
In addition, the programming model is simplified by making use of a
collection of client objects. Once the objects are populated by a successful batch
execution, all subsequent operations on the retrieved data are guaranteed to
succeed since they are stored in the object state. The programming model also
neatly makes the notification and query client semantics almost identical.
In addition, the GDI enables the following in subsequent iterations, or in
certain collections. First, the GDI enables the ability to dynamically discover new
data types through a standard type metadata system. It allows certain features such
as generic debugging interfaces and data query interfaces. Second, since all
collections have the same interface, they can easily be sandboxed in another
process or App-domain. Third, since all collections have the same interface, it
allows the system to put any collection in a maintenance mode and unload it by
implementing a call counting shim. This is extremely useful for setup when it
upgrades an existing component. Fourth, transactional support can be added quite
easily by allowing batches to also be transactional. Lastly, since all objects use the
same interface, patterns such a decorators can be added easily to the system. This
provides the potential to have the system be extended by third parties in a very
flexible manner.
Although the invention has been described in language specific to structural
features and/or methodological steps, it is to be understood that the invention
defined in the appended claims is not necessarily limited to the specific features or
steps described. Rather, the specific features and steps are disclosed as preferred
forms of implementing the claimed invention.



CLAIMS
1. One or more computer-readable media having computer-readable
instructions thereon which, when executed, provide a software architecture
configured to:
provide an interface configured to provide a generic data model;
provide asynchronous client dispatch which allows a client or client
application to begin a data request which immediately returns control to a client
thread; and
provide asynchronous server dispatch hi which a server can service requests
from the client asynchronously.
2. The one or more computer-readable media of claim 1, wherein the
software architecture is configured to provide cancellation in which calls which
are in progress on the server can be cancelled by the client at any time.
3. The one or more computer-readable media of claim 1, wherein the
software architecture is configured to provide batching in which a client can build
up an arbitrary sequence of actions and have the actions sent to the server as a
unit.
4. The one or more computer-readable media of claim 1, wherein the
software architecture is configured to provide transactional invocation in which a
batch of actions can be assigned semantics that it must execute entirely or not
change the state of the server.
5. The one or more computer-readable media of claim 1, wherein the
software architecture is configured to provide parallel invocation in which a batch
of actions can be assigned semantics that all items may execute in parallel.
6. The one or more computer-readable media of claim 1, wherein the
software architecture is configured to provide interception in which components
can be inserted into the architecture that can perform one or more of the following:
monitor an associated system, synchronously respond to the system or modify the
behavior of the system.
7. The one or more computer-readable media of claim 1, wherein the
software architecture is configured to provide reflection through which properties
that are supported by a given class of object can be retrieved.
8. The one or more computer-readable media of claim 1, wherein the
software architecture is configured to provide:
batching in which a client can build up an arbitrary sequence of actions and
have the actions sent to the server as a unit; and
transactional invocation in which a batch of actions can be assigned
semantics that it must execute entirely or not change the state of the server.
9. The one or more computer-readable media of claim 1, wherein the
software architecture is configured to provide:
batching in which a client can build up an arbitrary sequence of actions and
have the actions sent to the server as a unit; and
parallel invocation in which a batch of actions can be assigned semantics
that all items must execute in parallel.
10. The one or more computer-readable media of claim 1, wherein the
software architecture is configured to provide:
batching in which a client can build up an arbitrary sequence of actions and
have the actions sent to the server as a unit;
transactional invocation in which a batch of actions can be assigned
semantics that it must execute entirely or not change the state of the server; and
parallel invocation in which a batch of actions can be assigned semantics
that all items must execute in parallel.
11. The one or more computer-readable media of claim 1, wherein the
software architecture is configured to provide:
batching in which a client can build up an arbitrary sequence of actions and
have the actions sent to the server as a unit; and
interception in which components can be inserted into the architecture that
can perform one or more of the following: monitor an associated system,
synchronously respond to the system or modify the behavior of the system.
12. The one or more computer-readable media of claim 1, wherein the
software architecture is configured to provide:
batching in which a client can build up an arbitrary sequence of actions and
have the actions sent to the server as a unit; and
reflection through which properties that are supported by a given class of
object can be retrieved.
13. The one or more computer-readable media of claim 1, wherein the
software architecture is configured to provide:
batching in which a client can build up an arbitrary sequence of actions and
have the actions sent to the server as a unit;
reflection through which properties that are supported by a given class of
object can be retrieved; and
interception in which components can be inserted into the architecture that
can perform one or more of the following: monitor an associated system,
synchronously respond to the system or modify the behavior of the system.
14. One or more computer-readable media having computer-readable
instructions thereon which, when executed, provide a software architecture
comprising:
a printing system having:
a common data interface that serves as an interface to a batching
router, the .common data interface being configured to allow messages from
a client to be built up and dispatched to the batching router, and to send
responses to the client;
a batching router communicatively linked with the common data
interface and configured to receive messages that are passed in by the
common data interface and asynchronously dispatch the messages to one or
more collections;
a plug-in table that is configured to keep track of message handling
plug-ins that are configured to receive and process messages intended for
one or more collections; and
an interception table configured for use hi intercepting and
modifying messages targeted to one or more objects.
15. The one or more computer-readable media of claim 14, wherein the
messages can contain one or more operations that are destined to one or more
plug-ins.
16. The one or more computer-readable media of claim 14 further
comprising a message plug-in communicatively associated with the batching
router and configured to receive sets of messages from the batching router and,
where appropriate, send the messages to another device.
17. The one or more computer-readable media of claim 14 farther
comprising a message service communicatively associated with the batching
router and configured to receive messages and break the messages down into a
sequence of calls on a collection interface.
18. The one or more computer-readable media of claim 14 further
comprising a message service communicatively associated with the batching
router and configured to receive messages and break the messages down into a
sequence of calls on a collection interface, and wherein the message service is
configured to perform one or more of the following tasks:
assign messages to threads;
allow messages to be responded to in multiple, deferred calls;
retrieve appropriate data from objects in a collection to populate a message;
handle cancellation operations;
cache object instances;
transparently lock objects; or
perform reflection services for objects maintained in collections.
19. The one or more computer-readable media of claim 14 further
comprising one or more collections of objects, individual collections maintaining a
homogeneous set of objects,
20. The one or more computer-readable media of claim 14 further
comprising one or more collections of objects, individual collections maintaining a
homogeneous set of objects, wherein individual collections are implemented as a
COM interface that is retrieved from a DLL.

Documents:

http://ipindiaonline.gov.in/patentsearch/GrantedSearch/viewdoc.aspx?id=+MEw8sdyuZmsPx5bkNBwng==&loc=+mN2fYxnTC4l0fUd8W4CAA==


Patent Number 278813
Indian Patent Application Number 226/DEL/2006
PG Journal Number 01/2017
Publication Date 06-Jan-2017
Grant Date 30-Dec-2016
Date of Filing 27-Jan-2006
Name of Patentee MICROSOFT CORPORATION
Applicant Address ONE MICROSOFT WAY, REDMOND, WASHINGTON 98052, U.S.A.
Inventors:
# Inventor's Name Inventor's Address
1 ADRIAN F. MAXA ONE MICROSOFT WAY, REDMOND, WASHINGTON 98052, U.S.A.
2 MARK. A. LAWRENCE ONE MICROSOFT WAY, REDMOND, WASHINGTON 98052, U.S.A.
PCT International Classification Number G06F 3/12
PCT International Application Number N/A
PCT International Filing date
PCT Conventions:
# PCT Application Number Date of Convention Priority Country
1 11/077,514 2005-03-10 U.S.A.