|
||
Ad Astra provides an API for customers and third-parties to interface with the Astra Schedule and Platinum Analytics products. The API uses open standards and can be accessed by any tool set or language (.NET, Java, Python/Ruby, etc.).
The API is fairly small, but powerful. The two primary API calls are the Entity and Query API. With those two API calls you can get to just about any data you want in the system. The API can query all entities and relationships in the Ad Astra Database and perform complex searches. Further, there is a transaction API for updating data in the Ad Astra Database.
The Ad Astra API is best described as a RESTful web service1, using standard HTTP2 or HTTPS3. What this means in practice is that data contained in the Ad Astra Database can be queried using simple web requests. For example, http://schedule.xyz.edu/~api/entities/event/byid/... would request data for an event given its unique ID. This concept extends to data relationships as well as searches. Further, searches can be filtered using arbitrary expressions against any number of fields, sorted by multiple fields and broken into conveniently sized pages.
For efficiency and convenience for Web 2.0 applications, the data returned by the Ad Astra API is encoded using the JSON4 data format (described by RFC 46275). The http://json.org web site has links to dozens of open source and free libraries that can be used to easily decode JSON data in almost any language.
An “entity” in the Ad Astra Database is basically a row in a table, though not all tables are entity tables. All entities have certain fields in common:
•Id – The unique identifier of the entity (a GUID6).
•CreateDate – The datetime stamp set when the entity (row) was created.
•CreateBy – The Id of the User that created the entity.
•ModifiedDate – The datetime stamp of the last modification of the entity.
•ModifiedBy – The Id of the User that last modified the entity.
•RowVersion – An internal number used for transaction management.
Beyond these fields, many entities have a Name, but the other fields vary by table. The most concise, programmatic description available to describe an entity in Ad Astra is found in JavaScript format in the web application. These files are used by the Web UI to properly decode, validate, format and parse data for an entity. The location (relative to the virtual directory) for these files is:
/javascript/astra/entities
For example, the /javascript/astra/entities/Section.js file contains a description of the Section entity.
The most primitive information is the field description object. This information is equivalent to the table columns (more or less) found in the Ad Astra Database. For example, the Section entity contains these fields:
fields:
{
Id: { type:'string' },
TermId: { type:'string' },
CampusId: { type:'string' },
CourseDeliveryMethodId: { type:'string' },
SectionTitle: { type:'string' },
SectionCode: { type:'string' },
DoNotOptimize: { type:'boolean' },
DoNotOptimizeInstructor: { type:'boolean' },
DoNotOptimizeTime: { type:'boolean' },
CourseTitleId: { type:'string' },
IsCanceled: { type:'boolean' },
SISEnrollment: { type:'int' },
SISMaxEnrollment: { type:'int' },
AdjustedMaxEnrollment: { type:'int' },
CalculatedSeats: { type:'int' },
EstimatedEnrollment: { type:'int' },
CensusEnrollment: { type:'int' },
CensusEnrollmentDate: { type:'date' },
CensusEnrollment2: { type:'int' },
CensusEnrollmentDate2: { type:'date' },
IsPreReq: { type:'boolean' },
IsGradeable: { type:'boolean' },
EnrollmentWaiting: { type:'int' },
WaitingAvailCount: { type:'int' },
WaitingCapacity: { type:'int' },
IsLinkParent: { type:'boolean' },
IgnoreInAnalysis: { type:'boolean' },
ContainsCrossList: { type:'boolean' },
SISCrossEnrlError: { type:'boolean' },
LinkedSectionId: { type:'string' },
SisKey: { type:'string' },
LastSisUpdateDate: { type:'date' },
LastImportedDate: { type:'date' },
LastExportedDate: { type:'date' },
RequiresAttention: { type:'boolean' },
RequiresAttentionReason: { type:'string' },
IsActive: { type:'boolean' },
CreatedDate: { type:'date' },
CreatedBy: { type:'string' },
ModifiedDate: { type:'date' },
ModifiedBy: { type:'string' },
RowVersion: { type:'int' }
},
Beyond simple fields, the Ad Astra Database defines associations between entities and describes these associations in the same file. These are contained in a “join” portion of the metadata. For example, a Room has some of each kind of association:
join :
{
// one-to-one association (set)
Building : { field:"BuildingId", type:"Building" },
HvacZone : { field:"HvacZoneId", type:"HvacZone" },
RoomType : { field:"RoomTypeId", type:"RoomType" },
WorkflowDefinition : { field:"WorkflowDefinitionId",
type:"WorkflowDefinition" },
// one-to-many association (edit)
OptimizerSctMtgSuitRms : { type: "OptimizerSctMtgSuitRm",
isOwner: false, fkField: "RoomId", fkNullable: false},
PrefRuleRoomItems : { type: "PrefRuleRoomItem",
isOwner: true, fkField: "RoomId", fkNullable: false},
RoomConfigurations : { type: "RoomConfiguration",
isOwner: true, fkField: "RoomId", fkNullable: false},
RoomConflictsByConflictRoomId : { type: "RoomConflict",
isOwner: true, fkField: "ConflictRoomId", fkNullable: false},
RoomConflicts : { type: "RoomConflict", isOwner: true,
fkField: "RoomId", fkNullable: false},
RoomFeatureQuantities : { type: "RoomFeatureQuantity",
isOwner: true, fkField: "RoomId", fkNullable: false},
NotesByRecordId : { type: "Note", isOwner: true,
fkField: "RecordId", fkNullable: false,
fkTypeField: "NoteTypeId",
fkTypeValue: "ad950a8d-046b-4d25-b079-5845a0f5530a"},
RoomPartitionsByPartitionRoomId : { type: "RoomPartition",
isOwner: true, fkField: "PartitionRoomId",
fkNullable: false},
RoomPartitions : { type: "RoomPartition", isOwner: true,
fkField: "RoomId", fkNullable: false},
ProfileItemsByDataValueId : { type: "ProfileItem", isOwner: true,
fkField: "DataValueId", fkNullable: false,
fkTypeField: "ValueDataTypeId",
fkTypeValue: "759385fd-0d7c-4f3f-95d2-2f6757cb5606"},
TimetableSctMtgSuitRms : { type: "TimetableSctMtgSuitRm",
isOwner: false, fkField: "RoomId", fkNullable: false},
// many-to-many association (add/remove)
Regions : { relation: "RoomRegion", role: "RoomId",
type:"Region" },
AdHocRequestSettings : { relation: "AdHocRequestSttngRoom",
role: "AdHocRequestSettingId", type:"AdHocRequestSetting" }
}
One-to-One
The simplest form of association is an entity that contains the ID of another. The properties for this type of association are as follows:
•field – The name of the field that contains the ID value.
•type – The type of entity to which this ID refers.
One-to-Many
These entries describe other entities that contain an ID value related to this type.
•type – The type of the entity that contains an ID relating to this type.
•isOwner – True if this entity owns the entities that contain its ID. Such entities are deleted when the owning entity is deleted.
•fkField – The field in the related entity that contains the ID.
•fkNullable – True if the fkField in the related entity should be set to null when the related entity is deleted.
•fkTypeField – If present, this is the name of a field in the relating entity that contains a discriminating value that specifies the type to which it is related. For example, a Note can reference many different entity types. Which type a particular Note references is stored in its NoteTypeId field, so this property would contain “NoteTypeId” to describe this.
•fkTypeValue – This goes along with fkTypeField and is the value used to specify this type of entity. For example, the GUID “ad950a8d-046b-4d25-b079-5845a0f5530a” is stored in the NoteTypeId field of a Note to indicate that a Note references a Room.
Many-to-Many
To describe entities that relate in a many-to-many arrangement, the descriptor contains the following properties:
•type – The type of entity related to this type.
•relation – The name of the many-to-many association.
•role – The field name of this entity type in the association.
This type of association is often called a matrix association and the table containing the ID pairs is called a matrix table.
Using Entity Metadata
The data described above comes into play when accessing the Ad Astra API via the Query and Entity API’s.
The Query API is a simple ad-hoc query mechanism that can retrieve entities in whole or in part and with values obtained from related entities. This API is read only and is accessed using GET requests.
The Query API is accessed via an HTTP(S) GET to a URL off of the root of the virtual directory. The components of the URL are as follows:
~api/query/type?fields=Field1,Field2
&filter=foo
&sortOrder=foo
&search=foo
&searchField=foo
&start=0
&limit=25
The components that are replaced by real values are highlighted. The meaning of the various parameters are as follows:
•type – The entity type to retrieve (e.g., “section”).
•fields – The names of the fields to retrieve.
•filter – A filter expression to limit the returned entities. See Filter Expressions below for details.
•sortOrder – The names of the fields by which to sort the returned entities. This is a comma-separated list of field names prefixed by “+” or “-“ to sort ascending or descending, respectively. For example, “+Name,-ModifiedDate”.
•searchField – The field on which to search (must match the primary sort field).
•search – The value to find in the result set. The position where this value should occur (based on sortOrder) is returned along with the page that contained that value.
•start – The zero-based index of the first result entity the return. This is used to perform pagination and does not combine with search/searchField. The default is to start with the first item (0).
•limit – The number of items to return in the result set. This limits both start and search results. The default is to return all results.
The result is encoded based on the option view parameter whose default is “JSON”4. The object returned looks like this:
{
totalRecords: 427,
data: [
[ “A”,2,”xyz” ], // requested fields of first entity
...
// up to limit elements
]
}
The totalRecord property holds the total number of results, regardless of the “limit” parameter. The “data” property holds an array of arrays. Each array in the “data” array contains the field values based on the “fields” parameter.
Normally the “fields” parameter contains simple field names such as “Name” or “Capacity”. The fields, however, can reference related entities by using the dot-syntax. For example, to get the ID of each Room along with the Name of its Building and Campus, you would do this:
~api/query/room?fields=Id,Building.Name,Building.Campus.Name
The “Building” component of the second field is the name found in the “join” object that describes a one-to-one association from Room to Building. This is similar for the third field except that “Campus” is an association from the Building entity. The dot-syntax can follow as many association references as necessary.
The ability to control the entities returned in a query comes from the filter parameter. The syntax of a filter is C-like with some SQL extensions. The following C relational operators are supported:
== Equality
!= Inequality
< Less-than
<= Less-than or equal
> Greater-than
>= Greater-than or equal
&& Logical And
|| Logical Or
! Logical Not
& Bitwise AND
| Bitwise OR
In addition, the following SQL-style operators are supported:
?= Like (e.g. “Name ?= ‘Bob%th’”)
in IN (e.g. “Capacity in (2,4,8)”)
Parentheses are also supported. If no filter expression is given, then all entities of the specified type are returned.
The Entity API is similar to the Query API but returns complete entities in JSON object format.
The URL is also somewhat different. Consider a GET of the following URL:
~api/entity/room/byid/95ad0ad8-0b46-254d-07b9-50f5a845a530
This would return the Room entity given its ID (a GUID). The returned object will look something like this:
{
Id: “95ad0ad8-0b46-254d-07b9-50f5a845a530”,
Name: “HOLT 122”,
Description: null,
RoomNumber: 122,
KeyNumber: null,
PhoneAreaCode: null,
PhoneNumber: null,
PhoneExtension: null,
Width: 20.0,
Length: 30.0,
SquareFootage: 600.0,
MaxOccupancy: 25,
IsShareable: false,
MaxSharedActivities: 1,
PriorityId: null,
RoomTypeId: null,
BuildingId: “79D5F153-8695-4679-9819-303D584A01D8”,
HvacZoneId: null,
SisKey: “MAIN_HOLT_122”,
NoSchedule: false,
ArrangedSection: false,
DoNotOptimize: false,
LastSisUpdateDate: null,
LastImportedDate: null,
LastExportedDate: null,
RequiresAttention: false,
EffectiveStartDate: null,
EffectiveEndDate: null,
EffectiveParentId: null,
IsActive: true,
WorkflowDefinitionId: null,
CreatedDate: “2007-03-13T13:11:30Z”,
CreatedBy: “A394C903-7A6A-400d-936A-0C1142341DEB”,
ModifiedDate: null,
ModifiedBy: null,
RowVersion: 1
}
In the URL, the special token “byid” is used to request an entity given its ID.
The same part of the URL that contained “byid” above is also used to retrieve entities related to a particular entity. For example, to get all rooms for a building:
~api/entity/building/rooms/79D5F153-8695-4679-9819-303D584A01D8
The result of this API call is an array of Room entity objects like the one above. This can also retrieve a one-to-one associated entity in which case the result will be a single object and not an array.
The Entity API also supports a POST method. This is a non-RESTful API in that all POST requests are sent to the “~api/entity” URL, but this is required to perform a proper database transaction. The general structure of the posted object is as follows:
{
Room: {
$: [ // array of complete (current) entities
{ Id: "2178abfa-689d-49c8-9d1a-426561e3ab6f", Name: "...", ... }
],
"+": [ // array of new entities (inserts)
],
"%": [ // array of modified entities (updates)
],
"-": [ // array of deleted entity ids
],
// in this context, associations must be complete and do not support
// updates (add/remove)
Regions: { // association name
// map keyed by id of "base" entity (Room in this case)
"2178abfa-689d-49c8-9d1a-426561e3ab6f": [
// array of ids of the members
"7a6feb09-34ae-8cf1-737f-86ed721dfeba",
"1b1b08ac-ddfd-47a7-a1db-c6e702c0ce3a",
"2a6feb09-54af-4cf4-530f-ffed73456eb7",
...
]
}
},
// members of associations must be present in data
Region: {
$: [
{ Id: "7a6feb09-34ae-8cf1-330f-86ed721dfeba", Name: "...", ... }
]
},
// updates to FK associations are handled as changes to the appropriate
// entity, but many-to-many association changes are represented as "~associationName"
"~RoomRegion": {
"+": [ // array of added 2-tuples (arrays) of [RoomId,RegionId]
[ "2178abfa-689d-49c8-9d1a-426561e3ab6f", "1b1b08ac-ddfd-47a7-a1db-c6e702c0ce3a" ]
],
"-": [ // array of removed 2-tuples (arrays) of [RoomId,RegionId]
[ "2178abfa-689d-49c8-9d1a-426561e3ab6f", "7a6feb09-34ae-8cf1-737f-86ed721dfeba" ]
]
}
}
Inserts
The “+” object in the POST contains one or more objects per type of entity to be inserted. For example, to insert a Room, you might POST this:
Room: {
"+": [
{ Id: "2178abfa-689d-49c8-9d1a-426561e3ab6f", Name: "...", ... }
]
}
Since the Astra Database uses GUID’s to identify objects, a properly generated GUID is safe to supply from the client on a new entity.
Updates
Update requests look just like insert requests, except that the supplied objects are generally partial. This is because only the fields that need to be updated are given. All other fields remain unchanged.
Deletes
To delete an entity, you simply put its ID in an array under its type. For example, to delete the above room:
Room: {
"-": [
{ Id: "2178abfa-689d-49c8-9d1a-426561e3ab6f" }
]
}
Associations
To update many-to-many associations, the desired additions and removals are sent. For example, to add a room (whose Id is “95ad0ad8-0b46-254d-07b9-50f5a845a530”) to one region and remove it from another:
"~RoomRegion": {
"+": [ // array of added 2-tuples (arrays) of [RoomId,RegionId]
[ "95ad0ad8-0b46-254d-07b9-50f5a845a530", "1b1b08ac-ddfd-47a7-a1db-c6e702c0ce3a" ]
],
"-": [ // array of removed 2-tuples (arrays) of [RoomId,RegionId]
[ "95ad0ad8-0b46-254d-07b9-50f5a845a530", "7a6feb09-34ae-8cf1-737f-86ed721dfeba" ]
]
}
All modifications specified in the POST are performed as a single database transaction. If the request fails, no modifications will be made to the database. There are often side-effects to updates made via the Entity API, so the total set of modifications to the database may be (much) larger than given in the POST body.
1.http://en.wikipedia.org/wiki/Representational_State_Transfer
2.http://en.wikipedia.org/wiki/HTTP
3.http://en.wikipedia.org/wiki/HTTPS