We will start by looking at the framework and its components. The key components of a document service are as follows:
Of course, there's more to these components than the few words we've used here to describe them. We will now look at these components one by one, starting with the query.
Each document service is based on a query defined in the AOT. Using the AIF Document Service Wizard, a document class is generated and XML schema definitions that are used for the XML serialization are derived from the corresponding query. Therefore, the XML message will have a correlation to the query object. In the following screenshot, we can see the query for the InventItemService
document service:
If we look at an actual XML message, we can clearly see that it matches the structure of the query object. The following screenshot shows us the XML content after the serialization of the item's business object:
Document classes extend the AxdBase
class and represent a business document, for example, a sales order. They contain the business logic across all of the tables that correspond with the document. Hence, the details about the underlying tables are hidden from the consumer.
A document class also handles the generation of XML Schema Definition (XSD). The XSD schema defines the document structure and the business rules that are to be followed. Along with the generation of XSD, the document class also contains logic to serialize the table entity's classes into XML and deserialize them from XML.
Document classes have a number of responsibilities, and among them, we have the following:
Let's look at an example document class and analyze it to see how some of these responsibilities are actually handled. In this example, we will take a closer look at the sales order document class, AxdSalesTable
.
XSD is generated in the getSchema()
and getSchemaInternal()
methods of the AxdBase
class. The AxdBaseGenerationXSD
class is called to generate XSD based on this document class and its underlying table classes, as shown in the following code snippet:
private AifDocumentSchemaXml getSchemaInternal(Boolean _includeLabels, container _languageIds) { AxdBaseGenerateXSD genXsd; str documentClass ; AifDocumentSchemaXml schemaXml; genXsd = AxdBaseGenerateXSD::construct(); genXsd.parmIncludeLabels(_includeLabels); genXsd.parmLanguageIds(_languageIds); documentClass = new SysDictClass(classIdGet(this)).name() ; genXsd.setSharedTypesSchema(sharedTypesSchema); schemaXml = genXsd.generate(documentClass,this.getName(), this.getQuery()); sharedTypesSchema = genXsd.getSharedTypesSchema(); return schemaXml; }
XML serialization and deserialization is performed in several places depending on the operation that is being executed. Either the
axdBaseRead
,
axdBaseUpdate
, or axdBaseCreate
class is used to service the consumer's call. For example, take a look at the axdBaseRead
class that is used when performing a read operation. Here, you can find the serializeDocument()
method that is used to serialize the document into XML, as shown in the following code snippet.
protected AifDocumentXml serializeDocument(AifConstraintListCollection _constraintListCollection, boolean _calledFromRead) { ClassName documentName; Map propertyInfoMap; this.init(); this.setDocumentNameSpace(); documentName = axdBase.getName(); propertyInfoMap = this.getMethodInfoMap(classIdGet(axdBase)); axdXmlWriter.writeStartDocument(documentName); this.serializeClass(propertyInfoMap, axdBase); // Omitted code axdXmlWriter.writeEndDocument(); return axdXmlWriter.getXML(); }
In the preceding code, the following methods are used:
The AxdSalesOrder
document class contains logic to handle cross-table dependencies. The prepareForSave()
method is an example of this. This method is called for every record that is saved. Let's take a look at a small piece of the code that is used for the sales document and see how it handles logic across the SalesLine
and InventDim
tables:
case classNum(AxInventDim) : axInventDim = _axdStack.top(); axSalesLine = axInventDim.parentAxBC(); axSalesLine.axInventDim().resetInternalValues(); if (createRecord) { axSalesLine.salesLine().unLinkAgreementLine(); } else { //InventDimId marked as touched in update scenarios and we need //new InventDimId axSalesLine.clearField(fieldNum(SalesLine,InventDimId),false); } axInventDim.moveAxInventDim(axSalesLine.axInventDim()); axSalesLine.setInventDimIdDirtySaved(false); return true;
In the code, we can see the following:
InventDimId
field is blanked out so a new InventDimId
can be filled inInventDim
table class are copied to the SalesLine
table class, and the InventDim
field of SalesLine
is marked as dirty to be savedThe document class is also responsible for validating the business document and making sure that the business rules are enforced. An example of this can be found in the checkSalesLine()
method. This method is called from within the prepareForSave()
method to ensure that the SalesLine
record does not contain any values that conflict with the business rules. The following code snippet shows us how two of the business rules are validated:
salesLineOrig = _axSalesLine.salesLine().orig(); if (salesLineOrig.LineDeliveryType == LineDeliveryType::DeliveryLine) { if (_axSalesLine.parmSalesQty() != salesLineOrig.SalesQty) { // It is not allowed to change quantity on delivery schedule // order lines. error("@SYS133823"); } if (_axSalesLine.parmSalesUnit() != salesLineOrig.SalesUnit) { // It is not allowed to change sales unit on delivery schedule // order lines. error("@SYS133824"); } }
The code checks the SalesQty
and SalesUnit
fields when LineDeliveryType
is DeliveryLine
. If these fields do not match, an error is written to the Infolog.
AxBC classes can be seen as wrapper classes for tables as they provide an object interface to the underlying table. They manage data access from and to the underlying table and contain business logic that is otherwise contained on forms. They also provide a means to specify default values for fields. Another name for AxBC classes is Ax<Table> classes.
AxBC classes are optional. It is possible to have a document service in which the underlying tables have no corresponding AxBC classes. If so, the framework will use the AxCommon
class to perform read and write operations to the table. In this case, you will have to place your code in the Axd<Document>
class in the prepareForSave()
and prepareForSaveExtended()
methods.
One example that shows how AxBC classes are optional is value mapping. Value mapping can be set up in the processing options of an integration port. When this type of value mapping suffices, it is not necessary to create an AxBC class for value mapping purposes. In this case, an AxBC class becomes necessary only if you want to perform more elaborate value mapping than the standard setup allows you to.
Therefore, depending on what your needs are, you can choose not to create AxBC classes for the tables in your document service, or you can create them using the AIF Document Service Wizard. The wizard creates the AxBC classes only if the Generate AxBC classes option is selected.
The following are the responsibilities of an AxBC class:
initValue()
method of the table can be defaulted in the AxBC class.In the AxSalesLine
class, we can see that there is validation logic in the validateWrite()
method, as shown in the following code snippet:
protected void validateWrite() { if (this.validateInput()) { if (!salesLine.validateWrite(true)) { if (continueOnError) { error("@SYS98197"); } else { throw error("@SYS23020"); } } } }
The code shows us that the AxSalesLine
class also calls the validateWrite()
method on the underlying table. This is done to make sure that the validation rules on the table are adhered to.
In almost all AxBC classes, you will find a method called setTableFields()
. In the AxSalesLine
class, this method calls all of the setter methods present for the fields of the SalesLine
table, as shown in the following code snippet:
protected void setTableFields() { //<GMX> #ISOCountryRegionCodes /</GMX> super(); useMapPolicy = false; this.setAddressRefTableId(); [...] this.setCustAccount(); this.setCustGroup(); [...]
When you want to define the order in which the fields are set, you can modify the code and rearrange the setter methods into the sequence that you want. In the preceding code, you can see that the CustAccount
field is set first and then the CustGroup
field is set. This is because determining the CustGroup
field depends on the value of the CustAccount
field.
If we look at the
valueMapDependingFields()
method, we will see an example of how value mapping can be performed, as shown in the following code snippet:
protected void valueMapDependingFields() { ItemId valueMapedItemId; InventDim valueMapedInventDim; if (this.valueMappingInbound()) { if (salesLine.CustAccount && item) { [valueMapedItemId,valueMapedInventDim] = this.axSalesItemId(salesLine.CustAccount,item); this.parmItemId(valueMapedItemId); if (!InventDim::isInventDimEqualProductDim(EcoResProductDimGroupSetup::newItemId(salesLine.ItemId), valueMapedInventDim,InventDim::find(InventDim::inventDimIdBlank()))) { axInventDim.productDimensions(valueMapedInventDim); this.parmInventDimId(InventDim::findOrCreate(axInventDim.inventDim()).InventDimId); } } } }
The exact implementation of the previous code is unimportant, but you can clearly see that the axSalesItemId()
method performs the value mapping to determine the item's number. Then, the mapped item number is used on the SalesLine
record. Apart from the value mapping of the item number, a mapping for the inventory dimensions of the corresponding InventDim
record is also performed.
AxBC classes can also contain logic that sets default values on fields. An example of this is found in the setLineNum()
method, as shown in the following code snippet:
protected void setLineNum() { if (this.isMethodExecuted(funcName(), fieldNum(SalesLine, LineNum))) { return; } this.setSalesId(); if (this.isFieldSet(fieldNum(SalesLine, SalesId))) { if (!lineNum) { lineNum = SalesLine::lastLineNum(this.parmSalesId()); } lineNum += 1; this.parmLineNum(lineNum); } }
In the previous code, we can see the following:
setSalesId()
method makes sure that the sales order number's value is setlastLineNum()
method is used to determine the highest line number used at the timeSalesLine
recordService classes are classes that contain the operations used in the integration port of that document service. Only the operations needed by the business are available. All service classes extend the AifDocumentService
class and delegate their operations to the
AifDocumentService
class. For example, when the Read
operation is available on a service class, the implementation of the operation will call the ReadList()
method on the AifDocumentService
parent class.
The following operations are available:
Create
: This operation receives a document class and creates records as and when required. The return value is an AifEntityKeyList
object that contains a list of key/value pairs that reference a record.Delete
: This operation is used to delete records from the database. The IDs of the records to be deleted are passed as a parameter.Find
: This operation takes an AifQueryCriteria
parameter and queries the database. The return value is a document class that contains the resulting records.Findkeys
: This operation does the same thing as the find operation but returns an AifEntityKeyList
object, which contains only the IDs of the resulting records instead of all of the data.Read
: This operation takes an AifEntityKeyList
object as a parameter, reads the records from the database, and returns them in a document. This operation is typically used in combination with the FindKeys
operation that first returns the values that are needed as an input for the Read
operation.Update
: This operation takes an AifEntityKeyList
object that contains the IDs of the records to be updated. The second parameter is the document that contains the updated records.GetKeys
: This operation uses a document filter and returns the resulting document keys in an AifEntityKeyList
object.GetChangedKeys
: This operation also uses a document filter along with a DateTime
parameter to return the document keys of the documents that have changed.For our service operations to be available in the inbound and outbound port forms, a service class alone is not enough. The services framework requires that you create a service node in the AOT for the service and its service operations that you want to expose. This is true for document services but applies equally to custom services.
A service node allows you to create service contracts based on service classes in a flexible and customizable way. If you wish, you can create multiple service nodes for one service class, each with a different external name and a different set of service operations that are exposed. You can even specify the namespace for the service and change the names of the service operations.
52.15.42.128