วันเสาร์ที่ 5 กุมภาพันธ์ พ.ศ. 2554

Getting Version and License Info

Getting Version and License Info
The short story is there is some information you want about a Unity
server that's simply not available in the SQL database. Yes, this is
annoying and it is something that is going to be addressed in later
versions of Unity. If you've looked over the Data Object chapter
earlier you'll know quite a lot of data resides in the registry on the
local Unity server. Getting at the registry on a remote box is
possible but the rights issues are pretty painful. If you want to go
that route, feel free. There is, however, also licensing information
which is not available in SQL or in the registry and if you're going
to be adding or updating users you definitely need to have access to
this. The licensing information is especially tricky since you have
to deal not only with single boxes with their own set of license
limits but with the new "pooled licensing" available in 4.0(1) and
later where multiple Unity servers contribute and pull from a common
set of license data.

Fortunately there is a mechanism that allows you to hit a URL on the
Unity box in question and get an XML page back that has the most
critical information about the Unity server including selected data
from the registry and a full accounting of the licensing info on that
box that will take pooled licenses into account as well. If you're
running a production 4.0(1) server this function won't work for you.
If you have Unity 4.0(2) installed you'll need to apply the same files
before this will work. This was a last minute thing for 4.0(2) and it
wasn't quite out of the oven by the time it shipped to production so
you'll have to run the Cisco Unity Access Library (CUAL) setup
provided on the code samples page of www.CiscoUnityTools.com on the
Unity servers itself and restart the TomCat service in the service
control manager. You need to shut down the TomCat service, run the
setup and restart the TomCat service and you're done. Unity never has
to go off line and this is a safe patch since it's just adding an
additional web page you can address, it's not touching any core
functionality in Unity. If you're running 4.0(3) or later you're
golden as all these files are there and flying out of the box. Short
story, if you're running 4.0(2) and can't upgrade to 4.0(3) or later,
I recommend you run around and apply the setup provided for this on
all your Unity servers you want to create or import users on or you
want to connect to remotely.

The Remote Connection source sample provides modules that use the
server name to construct the URL to hit for both licensing and server
information pages provided by the CUAL setup and then shows how to
parse them out. If you're at all familiar with XML this is pretty
straight forward stuff. To get at the licensing information on a
particular server you construct a URL that looks like this:

http://MyUnityServer:8080/cual/CiscoUnitySystemInformation.jws?method=GetLicenseInformation

Where, of course, "MyUnityServer" is the name of the Unity server you
want licensing information on. You'd typically need this if you were
going to create or import a subscriber onto that box to be sure the
Class of Service you're adding the user to can take another subscriber
against it. Yes, the "8080" address is necessary for this to work
which can be a bit of a headache through firewalls and such but at the
moment there's no work around for this. When you hit that page in
your browser, it spits back a little SOAP wrapped XML text that looks
like this:

<?xml version="1.0" encoding="UTF-8" ?>
- <soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
- <soapenv:Body>
- <GetLicenseInformationResponse
soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<GetLicenseInformationReturn xsi:type="xsd:string"><?xml
version="1.0" standalone="yes"?><AvXmlLicData> <Licenses>
<LicLanguagesMax>6</LicLanguagesMax>
<LicRealspeakSessionsMax>2</LicRealspeakSessionsMax>
<LicSubscribersMax>100</LicSubscribersMax>
<LicUMSubscribersMax>100</LicUMSubscribersMax>
<LicVMISubscribersMax>100</LicVMISubscribersMax>
<LicVoicePortsMax>4</LicVoicePortsMax> </Licenses> <Utilization>
<AvLicUtilizationSecondaryServer>0</AvLicUtilizationSecondaryServer>
<AvLicUtilizationSubscribers>51</AvLicUtilizationSubscribers>
<AvLicUtilizationVMISubscribers>0</AvLicUtilizationVMISubscribers>
</Utilization> <ErrorAlerts>0</ErrorAlerts>
<WarningAlerts>1</WarningAlerts></AvXmlLicData></GetLicenseInformationReturn>
</GetLicenseInformationResponse>
</soapenv:Body>
</soapenv:Envelope>

Short story it shows you the total counts available to that server and
how many it's using. If the Unity server in question is part of a
pool of licenses, the total counts will reflect that for you. When
going to add a user to that box you just need to check if the
subscriber count can take one more and if the COS you're associating
the user with has VMI enabled you have to check that there's available
seats for that license and if the COS has TTS enabled you have to make
sure there are TTS sessions enabled. Technically the COS shouldn't be
able to have TTS enabled if there are no TTS sessions defined but it's
possible to do this programmatically by accident so it's proper to
check.

In the dump above you can eyeball it real quick and see that it's a 4
port system with 2 sessions of TTS, 100 subscribers with 51 used up so
far. However you may (as many do) puzzle over the
"<LicSubscribersMax>100</LicSubscribersMax>" and
"<LicUMSubscribersMax>100</LicUMSubscribersMax>" both being in the
same dump. What does this mean? Currently in Unity the licensing
scheme does not allow for mixing UM and VM seats - it's an
all-or-nothing deal. Yes, I know this is annoying, you're preaching
to the choir here. However, in the above license dump that's a UM
configuration since all 100 seats are "UM enabled" which is what the
two counts mean. If this had been a VM only configuration you'd see
100 LicSubscribersMax and 0 for LicUMSubscribersMax. Some day they
may allow mixing licenses on the same box and you'll see a total
subscriber count and a UM count that's a subset.

To pull out the license information is just an exercise in XML parsing
which is pretty straight forward stuff using the MSXLM 3.0 libraries
which we walk through for the system information section below if you
want to go through the details. There's a "checkForAvailableLicense"
routine in the Remote Connection sample project that wraps things up
nicely for you. All you have to pass in is the AccessTTS and
AccessVMI flags on the COS you want to add the user to and it'll
return TRUE or FALSE to tell you if you can add a user to the box with
that COS or not. Here's a chunk of code showing how to use that
routine.

'First, you need to get the AccessVMI and AccessTTS columns from the
row in the COS table for the class of service you'll be adding the
user to. You can pull the COSObjectID off the subscriber template
you're using to create the subscriber using this query.
Set rsCOS = New ADODB.Recordset
rsCOS.Open "SELECT AccessTTS, AccessVMI FROM vw_COS INNER JOIN
vw_SubscriberTemplate ON
vw_SubscriberTemplate.COSOBjectID=vw_COS.CosObjectID WHERE
vw_SubscriberTemplate.Alias='" + strTemplateAlias+ "'",
strConnectionString, adOpenKeyset, adLockReadOnly

If checkForAvailableLicense(strUnityServerName,
rsCOS("AccessTTS"), rsCOS("AccessVMI")) = False Then
Msgbox "No licenses available for that COS, you cannot add a new user to it"
Else
<...code to add subscriber...>
End If

If the INNER JOIN used in the query there is new to you, it'd probably
be a good idea to go fetch the "SQL Sample Queries" code example off
www.CiscoUnityTools.com and spend some time walking through it. A
decent book on introductory SQL queries (this is not Microsoft SQL
specific by any means) would also not be a bad idea. Getting
comfortable with both INNER and OUTER JOINs is critical to doing much
of anything useful in a database where information about a particular
object is strung across multiple tables (which describes just about
any database of even medium complexity out there).

There are other pieces of information you're going to be interested in
about a Unity server other than licensing that are not available in
SQL at this point. As noted above these items will find their way
into SQL at some point but for now you have to ping the
"GetSystemInformation" method off the same web page we get the
licensing information off of to get at such things as the Unity
version, what back end messaging system is being used, which messaging
server we're connected to for our "partner" server and the like. To
see this info you can ping the following web address where, again,
"TestBox" is the name of your Unity server:

http://TestBox:8080/cual/CiscoUnitySystemInformation.jws?method=GetSystemInformation"

The browser will show the following data when you hit that site:

<?xml version="1.0" encoding="UTF-8" ?>
- <soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
- <soapenv:Body>
- <GetSystemInformationResponse
soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<GetSystemInformationReturn xsi:type="xsd:string"><?xml
version="1.0" encoding="UTF-8"?>
<CiscoUnitySystemInformation><Version>4.00.01.54</Version><DirectoryType>AvDsAD.AvDsAD.1</DirectoryType><MailServer>LINDBORG-SPEEDY</MailServer><Integrations><Integration
name="Cisco CallManager"
number="1"/></Integrations></CiscoUnitySystemInformation></GetSystemInformationReturn>
</GetSystemInformationResponse>
</soapenv:Body>
</soapenv:Envelope>

This is very similar to the licensing data above in that it has a SOAP
"envelope" around some XML text. To parse out the information using
the Microsoft MSXML 3.0 libraries in VB it looks like this:

'trim off the garbage that comes at the beginning and end of the
page sometimes - the XML libraries don't appreciate it even though IE
doesn't seem to have a problem with it.
strXML = removeLeadingWhitespace(GetURL(strURL, INTERNET_OPEN_TYPE_DIRECT))

'Since the information comes in wrapped in a SOAP "envelope" you
actually need to open the xmlDOC against it and then open another
xmlDOC from that to get the data within the SOAP wrapper. I didn't
want to require folks to have the SOAP libraries on their client
machines here so I'm digging the info out of it "XML Old School"
style. If you want to use SOAP libraries directly, feel free.
xmlDoc.loadXML strXML
If xmlDoc.parseError.errorCode <> 0 Then
MsgBox "There was a problem with the system information data from the
server. This is likely caused because you have not applied the CUAL
patch to your Unity 4.0(2) Unity server that you're querying here."
Exit Function
End If

'Snag the actual XML "meat" out of the SOAP sandwich currently in the
xmlDOC object.
xmlDoc.loadXML xmlDoc.documentElement.Text
If xmlDoc.parseError.errorCode <> 0 Then
MsgBox "There was a problem with the system information data from the
server. This is likely caused because you have not applied the CUAL
patch to your Unity 4.0(2) Unity server that you're querying here."
Exit Function
End If

'Get at the information in the XML string and dump it out. In the
Remote Connection code example this info is added to a dictionary and
passed back to the calling routine for processing.
MsgBox "Version: " + xmlDoc.getElementsByTagName("Version").Item(0).Text

'The directory type can be "AvDsAD.AvDsAD.1" for Active Directory
(Exchange 2000/2003), or "AvDsEx55.AvDsEx55.1" for Exchange 5.5 or
"AvDsDOM.AvDsDOM.1" for Domino.
MsgBox"DirectoryType: "
xmlDoc.getElementsByTagName("DirectoryType").Item(0).Text

'The mail server name is the partner server Unity is connected to that
you selected during the setup.
MsgBox "MailServer: ",
xmlDoc.getElementsByTagName("MailServer").Item(0).Text

'There can be more than one switch integration, as such it's a
collection under the Integrations node. This output method I'm using
here is rather silly and just throws up a message box for each switch
found as Switch0, Switch1, Switch2 etc... Currently there are only two
integrations allowed on a particular Unity but that will likely change
soon.
Set nodesInfo =
xmlDoc.getElementsByTagName("Integrations").Item(0).childNodes

iCounter = 0
For Each nodeCurrent In nodesInfo
MsgBox "Switch: " + Trim(Str(iCounter)),
nodesInfo.Item(0).Attributes(0).Text
iCounter = iCounter + 1
Next

The licensing URL will remain indefinitely but as we move forward,
later versions of Unity, data now stored in the registry will migrate
into tables in SQL and the need to hit the Unity information URL will
go away. However at the time of this writing Unity 4.0(4) still
stores much of the above information in the registry.
Find all Unity servers in the enterprise
The nice thing about the design of Unity is that you only need to
connect to one Unity server in the directory and it'll have all
information about other Unity servers and subscribers you need to find
and connect to them. The directory monitors have done all the heavy
lifting for you here and you can exploit it easily.

As discussed in the Architectural Overview and the Data Object Model
chapters, information about remote Unity servers and remote
subscribers homed on those servers can be found in the global location
and global subscriber tables in UnityDB. If the other Unity server is
in the same directory, information about it and it's subscribers will
be available to you.

In this case we just want to get a full list of all "visible" Unity
servers on the network. Once you have your connection string
constructed as described earlier, you simply need to open a recordset
using this query:

rsUnityServers.Open "SELECT HomeServer FROM vw_Location_Global GROUP
BY HomeServer", strConnectionString, adOpenKeyset, adLockReadOnly

The rsUnityServers recordset now includes a complete list of all Unity
server names visible on the network. Yes, there is another
"HomeServer" column in the subscriber table that throws some folks
off. The HomeServer value in the subscriber table is the server name
of the mail store server the subscriber's mailbox is homed on, not
their home Unity server. The wisdom of using the same column name for
two different concepts like that is left as an exercise for the
reader.
Listing and finding subscribers anywhere in the enterprise
In much the same way information about remote Unity servers can be
found in the global location table, information about remote Unity
subscribers can be found in the global subscriber table. As we
discussed extensively in the Data Object Model chapter, there's a lot
of information about a subscriber that doesn't get replicated around
the directory such as most of the data associated with their primary
call handler. For a complete run down on what info we have on remote
subscribers, whip open your copy of CUDLE and look through the columns
for the GlobalSubscriber table.

For the first part of this example we're going to generate one table
that includes the alias, first name, last name, display name, primary
extension and home Unity server for each subscriber in the directory.

SELECT vw_Subscriber_Global.Alias, vw_Subscriber_Global.DisplayName,
vw_Subscriber_Global.FirstName, vw_Subscriber_Global.LastName,
vw_Subscriber_Global.DTMFAccessID AS 'Primary Extension',
vw_Location_Global.HomeServer
FROM vw_Subscriber_Global INNER JOIN vw_Location_Global
ON vw_Subscriber_Global.LocationObjectID=vw_Location_Global.LocationObjectID
WHERE vw_Subscriber_Global.SubscriberType NOT IN (0,6)

Since the home Unity server name is stored in the global location
table we need to do an inner join here to get at it. The
subscriberType filter in the WHERE clause specifically excludes the
special built in installer account (type 6) and the Unity Messaging
System account (type 0) which are not Unity subscribers but special
objects used internally. As such the resulting recordset contains
information about all subscribers including full Exchange and Domino
subscribers as well as internet subscribers (AMIS, SMTP, Bridge and
VPIM users). If you want to filter out all but full subscribers you
could replace that with "WHERE vw_Subscriber_Global.SubscriberType IN
(1,3)" for instance. Again, check CUDLE for the SubscriberType column
and you'll get a full run down on what all the legal values for this
column are and what they mean.

If you want to find all the subscribers in the directory that are on
Unity servers in the same dialing domain, this is easily done by
adding another simple JOIN clause into the query. Dialing domains are
defined as all primary location objects that have a matching
DialingDomainName string associated with them. As such adjusting the
query to look like this:

SELECT vw_Subscriber_Global.Alias, vw_Subscriber_Global.DisplayName,
vw_Subscriber_Global.FirstName, vw_Subscriber_Global.LastName,
vw_Subscriber_Global.DTMFAccessID AS 'Primary Extension',
vw_Location_Global.HomeServer
FROM vw_Subscriber_Global INNER JOIN vw_Location_Global
ON vw_Subscriber_Global.LocationObjectID=vw_Location_Global.LocationObjectID
INNER JOIN vw_Location
ON vw_Location.DialingDomainName=vw_Location_Global.DialingDomainName
WHERE vw_Subscriber_Global.SubscriberType NOT IN (0,6) and
vw_Location.Alias='default'

The location alias of 'default' is a handy way of grabbing the local
primary location which is where the dilaing domain name is stored.
This query will grab all the Unity subscribers homed on any Unity
server that's in the same dialing domain as the Unity server you're
attached to when running the query. Remember that if the Unity server
you're attached to is not part of a dialing domain, the resulting
recordset will contain no members because the DialingDomainName column
for the local location is NULL and in SQL land NULL <> NULL and so
there will be no matching rows. To deal with this you can explicitly
check for "IsNull(vw_Location.DialingDomainName)" and execute an
appropriate query.

To get a list of all the dialing domains in the directory is similar
to getting all the server names:

SELECT DialingDomainName FROM vw_Location_Global GROUP BY DialingDomainName

You could take this subscriber recordset you created above and bind it
to a grid control and have yourself a pretty decent subscriber browser
for very little work. You can easily throw a tree control on there to
adjust the query to include users only homed on specific Unity servers
or servers within a dialing domain. Combine this with the ability to
directly open a subscriber's SA page off their home Unity server
discussed in the next section and you're on your way to making your
very own Global Subscriber Manager.

While we're rummaging around in the global subscriber and global
location tables to find information about remote users, there are a
couple common problems that come up when dealing with the enterprise
level directory we can solve easily. First, if you have a user in
your directory it's good to know which Unity server they live on such
that you can launch their SA page directory or you know which Unity
server you need to attach to so you can update their information or
delete them or what have you. Assuming you have their mail alias from
the directory to start with, this is easily solved with a simple query
on our good friends the global tables:

SELECT vw_Subscriber_Global.DisplayName, vw_Location_Global.HomeServer
FROM vw_Subscriber_Global INNER JOIN vw_Location_Global
ON vw_Subscriber_Global.LocationObjectID=vw_Location_Global.LocationObjectID
WHERE vw_Subscriber_Global.Alias='jlindborg'

It gets a little trickier to find someone from their extension number.
The primary extension number of a subscriber is stored in the global
subscriber table which is easy enough to deal with using the view
(remember this column was removed in 4.0(3) in the raw table).
However subscribers can have up to 9 alternate extensions and you
can't be certain which one the user may be looking for. For this you
have to add a join to the DTMFAccessID table itself. As information
about remote Unity subscribers is pulled into the database, the
directory monitor unpacks the alternate extension number "blob" that's
added to the subscriber's directory object and puts the resulting
values into the DTMFAccessID table.

If the extension of the user you were looking for was "4321" the query
would look like this:

SELECT vw_Subscriber_Global.DisplayName, vw_Location_Global.HomeServer
FROM vw_Subscriber_Global INNER JOIN vw_Location_Global
ON vw_Subscriber_Global.LocationObjectID=vw_Location_Global.LocationObjectID
INNER JOIN vw_DTMFAccessID
ON vw_DTMFAccessID.ParentObjectID=vw_Subscriber_Global.SubscriberObjectID
WHERE vw_DTMFAccessID.DTMFAccessID='4321'

Be aware that it's perfectly legal for the above query to return more
than one match. The global subscriber table will include users from
all over the directory, not just those in a dialing domain. As such
it's possible for there to be overlapping numbering plans in the
corporate directory. You can include restrictions in your query for
only getting users in a particular dialing domain or you can just
present a list of display names and aliases and let the user running
your application make a smart decision.
Find an extension anywhere in the dialing domain with Unity 4.0(3) or later
In the previous section we talked about how to find the extension of a
subscriber anywhere in the directory. However sometimes you just want
to find any and all objects using a particular extension number in the
dialing domain. This can include local objects such as call handlers,
interview handlers and name lookup handlers. It can include global
objects like distribution lists, location objects and, of course,
subscribers in the dialing domain.

You'll need to make such a check any time you want to add a new object
with an extension defined or edit a standing object's extension to be
sure you don't introduce a conflict in the dialing domain. The Unity
conversation assumes that all extensions across all objects are unique
in the dialing domain and you'll get "undefined behavior" as they say
if you don't take care to avoid such conflicts.

For Unity 4.0(3) a new view was added that greatly simplifies this
process which is why I include two separate sections for how to do
this. The following code chunk grabs a new extension number off the
txtNewExtension text control on the form and checks to be sure the
extension in there doesn't conflict with anything in the dialing
domain. Checks on the validity of the text in the edit box has been
done further up stream, this just shows the mechanism for reporting
conflicts to the user in a reasonable way.

rsTemp.Open "SELECT * FROM vw_DTMFaccessID_DialingDomain WHERE
DTMFAccessID='" + txtNewExtension.Text + "'", strConnectionString,
adOpenKeyset, adLockReadOnly

If rsTemp.RecordCount > 0 Then
'conflict found in the dialing domain - report this to the
user. Technically there should only ever be one but it's possible the
site has other conflicts that pre exist so iterate through the
recordset and produce a message box that list all conflicts found,
what object.
strTemp = "That extension cannot be used since it conflicts
with the following objects in the dialing domain: "
rsTemp.MoveFirst
Do While rsTemp.EOF = False
strTemp = strTemp + "Extension= " + rsTemp("DTMFAccessID")
+ " is owned by " + strGetObjectTypeDescription(rsTemp("ObjectType"))
+ " with a Display Name= " + rsTemp("DisplayName") + ". "
rsTemp.MoveNext
Loop

MsgBox strTemp
End If

The strGetObjectTypeDescription function used there is a simple case
statement that translates the ObjectType number into a readable
string. As always, a quick trip to CUDLE will show the list of values
possible here, but here's the listing for that function so you know
what's going on here - nothing too complicated.

Public Function strGetObjectTypeDescription(iObjectType As Integer) As String
Dim strTemp As String

Select Case iObjectType
Case 1
strTemp = "Subscriber"
Case 2
strTemp = "Distribution List"
Case 3
strTemp = "Call Handler"
Case 5
strTemp = "Inteview Handler"
Case 6
strTemp = "Directory Handler"
Case 9
strTemp = "Location Object"
Case Else
strTemp = "Unknown"
End Select

strGetObjectTypeDescription = strTemp

Exit Function
End Function

That's it, you're done. Piece of cake. It's party time.

Find an extension anywhere in the dialing domain with Unity 4.0(1) or 4.0(2)

If you're running 4.0(1) or 4.0(2) and you simply can't upgrade to
4.0(3) or later, you'll need to do this check "old school". Finding
the ID you want and getting the information about that object can be a
little tricky since you'll have to check multiple places with a partty
gnarly query. It also requires you know what all the
ParentObjectIDType values in the DTMFAccessID table actually mean. A
quick trip to CUDLE will get you a list of all 6 values we care about
in this situation (the same ones we use in the CASE statement in the
previous section):

1 = Subscribers
2 = Public Distribution Lists
3 = Call Handlers
5 = Interview Handlers
6 = Directory Handlers (name lookup handlers)
9 = Location Objects

Call handlers, interview handlers and directory handlers are local to
the Unity server they are created on so finding them is pretty
straight forward stuff. Locations can span dialing domains and, as
such, their Ids need to be unique across all Unity servers that are in
their dialing domain. Subscribers also span dialing domains but
include alternate extensions and so require special consideration.
Distribution lists are global all the time which can introduce special
problems in a directory that spans dial plans.

Fortunately all you have to worry about is checking the DTMFAccessID
table which consolidates all the extensions, both local and global, in
one place to make such checks fairly easy. However if you want to
construct a decent looking list of display names and basic object
information to display to the user should a conflict arise, there's a
little more work involved.

The first check we'll do here is against all subscribers in the
dialing domain. We need to be careful here since if the Unity server
we're connected to is not part of any dialing domain, the
DialingDomainName value will be NULL. If the value is NULL then you
can just check against the local subscriber table instead of doing an
inner join against the global subscriber and global locations table as
you would normally.

First, get the dialing domain name the local box is in, if any.
Rstemp.open "SELECT DialingDomainName FROM vw_Location WHERE
vw_Location.Alias='default'",strConnectionString, adOpenKeyset,
adLockReadOnly
If IsNull(rsTemp("DialingDomainName")) then
StrDialingDomainName=vbNullString
Else
StrDialingDomainName=rsTemp("DialingDomainName")
EndIf

RsTemp.Close

If len(strDialingDomainName)=0 Then

strSQL = "SELECT vw_DTMFAccessID.DTMFAccessID,
vw_Subscriber.Alias, vw_Subscriber.DisplayName FROM vw_DTMFAccessID
INNER JOIN vw_Subscriber ON
vw_DTMFAccessID.ParentObjectID=vw_Subscriber.SubscriberObjectID WHERE
vw_Subscriber.SubscriberType NOT IN (0,6) AND
vw_DTMFAccessID.DTMFAccessid='" + strDTMFID + "'"

Else

strSQL = "SELECT vw_DTMFAccessID.DTMFAccessID,
vw_Subscriber_Global.Alias, vw_Subscriber_Global.DisplayName,
vw_Location_Global.HomeServer FROM vw_DTMFAccessID INNER JOIN
vw_Subscriber_Global ON
vw_DTMFAccessID.ParentObjectId=vw_Subscriber_Global.SubscriberObjectId
INNER JOIN vw_Location_Global ON
vw_Location_Global.LocationObjectId=vw_Subscriber_Global.LocationObjectId
WHERE vw_Subscriber_Global.SubscriberType NOT IN (0,6) AND
vw_Location_Global.DialingDomainName='" + strDialingDomain + "' AND
vw_DTMFAccessID.DTMFAccessid='" + strDTMFID + "'"
End If

rsTemp.Open strSQL, strConnectionString, adOpenKeyset, adLockReadOnly

If rsTemp.RecordCount > 1 Then
<...code to report conflicting subscribers to user...>
End If

The rsTemp recordset would contain all subscribers in the dialing
domain that had a primary or alternate ID that conflicted with the ID
you passed into the routine. If the server was not a member of any
dialing domain the result set would just contain local subscribers.
If there was one or more matches then you'd have to present an error
to the user indicating they can't use the ID they've selected for a
new object or for updating the extension of an existing object. You
can do this by constructing a dictionary or an array of structures or
whatever paints your wagon. The structure would contain the
information such that you could display a decent error message
indicating which subscriber on what server was causing the conflict.

Next the routine would need to check location objects which optionally
have Ids as well.

If Len (strDialingDomainName)=0 then

StrSQL="SELECT vw_Location_Global.TextName,
vw_Location_Global.Alias, vw_DTMFAccessID.DTMFAccessID
FROM vw_Location_Global INNER JOIN vw_DTMFAccessID
ON vw_Location_Global.LocationObjectID=vw_DTMFAccessID.ParentObjectId
WHERE vw_DTMFAccessID.DTMFAccessid='" + strDTMFID + "'"

Else

StrSQL="SELECT vw_Location_Global.TextName,
vw_Location_Global.Alias, vw_DTMFAccessID.DTMFAccessID
FROM vw_Location_Global INNER JOIN vw_DTMFAccessID
ON vw_Location_Global.LocationObjectID=vw_DTMFAccessID.ParentObjectID
WHERE vw_Location_Global.DialingDomainName='"+strDialingDomainName+"'
AND vw_DTMFAccessID.DTMFAccessID='"+strDTMFID+"'"

EndIf

rsTemp.Open strSQL, strConnectionString, adOpenKeyset, adLockReadOnly

If rsTemp.RecordCount > 1 Then
<...code to report conflicting subscribers to user...>
End If

RsTemp.Close

Now the structure that you're adding to for all potential conflicts
will contain all subscribers and distribution lists in the dialing
domain (if any) the local Unity server is a part of. The last thing
you need to do is check all objects that remain. Public distribution
lists are "shared" objects and can as such be considered local to all
Unity servers. This concept is a little tricky for folks sometimes.
The short story is if you assign an ID to a public distribution list,
that ID is used by ALL Unity servers in the entire corporate
directory. This is a common source of ID conflicts folks run into.
As a general rule don't assign Ids to public distribution lists unless
you're sure you have a coordinated dialing plan across your network.

To do this grab all objects referenced in the DTMFAccessID table that
are not subscribers or location objects that are using the ID in
question. By definition these are all the "local" objects and any ID
to be used by any other object on this server should not conflict with
these Ids. Iterate over the list of objects found (if any) and add
descriptive information about each object (i.e. it's type, it's
display name and alias at a minimum) into the structure you're using
to pass this data back to the calling party. The following code chunk
is a simplified version of what you'll find in the Add Subscriber
example:

strSQL = "SELECT * from DTMFAccessID WHERE DTMFAccessID='" + strDTMFID
+ "' AND ParentObjectIDType NOT IN (1,9)

rsTemp.Open strSQL, strConnectionString, adOpenKeyset, adLockReadOnly

If rsTemp.RecordCount > 0 Then
Do While rsTemp.EOF = False
Select Case rsTemp("ParentObjectIDTYpe")
Case 2
rsFind.Open "Select Alias, DisplayName from
DistributionList WHERE SystemDListObjectID='" +
rsTemp("ParentObjectID") + "'", strConnectionString, adOpenKeyset,
adLockReadOnly
<...insert code for reporting distribution list conflict...>
rsFind.Close
Case 3
'Yes, subscriber's have their DTMFAccessID for their primary
extension stored in their primary call handler and you'd think that
perhaps this would pick that up and do a "double check". However in
the DTMFAccessID table the parent of an extension associated with a
subscriber is a subscriber, not a call handler. As such this check is
only going to pick up local application call handlers as we want.
This is a little confusing since in other places such as calls to the
PHGreeting and PHTransfer conversations, references to subscribers are
done through their primary call handler.
rsFind.Open "Select Alias, TextName from
CallHandler WHERE CallHandlerObjectID='" + rsTemp("ParentObjectID") +
"'", strConnectionString, adOpenKeyset, adLockReadOnly
<...insert code for reporting call handler conflict...>
rsFind.Close
Case 5
rsFind.Open "Select Alias, TextName from
InterviewHandler WHERE InterviewHandlerObjectID='" +
rsTemp("ParentObjectID") + "'", strConnectionString, adOpenKeyset,
adLockReadOnly
<...insert code for reporting interview handler conflict...>
rsFind.Close
Case 6
rsFind.Open "Select Alias, TextName,
NameLookupHandlerObjectID from NameLookupHandler WHERE
NameLookupHandlerObjectID='" + rsTemp("ParentObjectID") + "'",
strConnectionString, adOpenKeyset, adLockReadOnly
<...insert code for reporting name lookup handler conflict...>
rsFind.Close
Case 9
rsFind.Open "Select Alias, TextName,
LocationObjectID from GlobalLocation WHERE LocationObjectID='" +
rsTemp("ParentObjectID") + "'", strConnectionString, adOpenKeyset,
adLockReadOnly
rsFind.Close
Case Else
MsgBox "Error! Invalid ParentObjectID type in DTMFAccessID table!"
End Select
End With
rsTemp.MoveNext
Loop
End If

After performing all three checks your dictionary or array or whatever
you selected as your mechanism for reporting conflicts back to the
calling routine will contain ALL conflicts with the selected ID within
the dialing domain. This will be a common need for folks doing
subscribers adds, imports and updates so it's a good idea to get this
routine in place the way you want it early in your development cycle.

--
*

ข้อสอบครูชำนาญการพิเศษ *
ข้อสอบครูผู้ช่วย,สอบบรรจุครูผู้ช่วย,แนวข้อสอบครูผู้ช่วย,สอบครูผู้ช่วย,แนวข้อสอบครูผู้ช่วย,ครูผู้ช่วย,ข้อสอบบรรจุครู,ข้อสอบครูชำนาญการพิเศษ
*http://www.oopps.bloggang.com*
*
* ฟิสิกส์ ข้อสอบฟิสิกส์ บทเรียนฟิสิกส์ ฟิสิกส์ออนไลน์
โจทย์ฟิสิกส์ แบบฝึกหัดวิชาฟิสิกส์ โจทย์วิชาฟิสิกส์ โจทย์วิชาฟิสิกส์
http://thaiphysics.blogspot.com


* บทเรียนบทรัก ความรัก บทความความรัก ดูดวงความรัก นิยามความรัก
กลอนรัก เพลงรักhttp://love8love9.blogspot.com


* ความรัก บทความความรัก ดูดวงความรัก นิยามความรัก กลอนรัก
เพลงรักhttp://kongkoymusic.blogspot.com

ไม่มีความคิดเห็น:

แสดงความคิดเห็น