Skip Headers
Oracle® Fusion Applications Developer's Guide
11g Release 5 (11.1.5)

Part Number E15524-10
Go to Documentation Home
Home
Go to Book List
Book List
Go to Table of Contents
Contents
Go to Feedback page
Contact Us

Go to previous page
Previous
Go to next page
Next
PDF · Mobi · ePub

5 Developing Services

Service-oriented development is based on the concept of services. It is the realization of business functionality via software that customers can use to compose new business applications by using existing services in the context of new or modified business processes. This chapter describes how you should design and develop the services to make them useful for both Oracle Fusion Applications and for customers. It also covers how the services are consumed.

This chapter contains the following sections:

5.1 Introduction to Services

A service is defined in terms of its interface, which is the way the service is exposed to the outside world. The service interface includes a set of operations the service supports, a set of parameters (defining data required for interaction with the service), and communication protocol used for data transfer and actual service invocation. Grouping of the methods in the service interface is defined by business functionality of the service.

Services have the following characteristics:

In Oracle Fusion, applications use both ADF Business Components services and service-oriented architecture (SOA) services. ADF Business Components services should be created to manage business objects and SOA services are for orchestration and business processes. SOA services use business object services to encapsulate business processes as illustrated in Figure 5-1:

Figure 5-1 SOA Service — Business Service

SOA Service - Business Process

This chapter focuses on business object services that are implemented using ADF Business Components services.

In Oracle Fusion, you make business objects and related business logic available via user interfaces (UIs) and services. A single general-purpose service can satisfy multiple use cases such as:

5.2 Designing the Service Interface

A service is a public interface, and it requires careful design to make it useful. For ADF Business Components services, on which this chapter focuses, the purpose of these services is managing business objects, including the generic lifecycle of the objects (create/update/delete/query), and special actions that can be applied to the objects.

Therefore, designing these services will start from identifying the business objects, continue with identifying service operations on business objects, and grouping the operations into services. Exceptions, warnings, or informational messages need to be defined for each operation and service as part of the service interface design process.

5.2.1 Identifying Business Objects

Identify which business objects that you want to expose from the service interface.

5.2.1.1 Business Object Attributes

The shape of the business object is very important. You need to include all the attributes that represent the objects, but exclude anything that is of no business value.

The business object must contain the following attributes:

  • Primary key attributes, including the system generated surrogate keys when defined.

  • Attributes that are of business value to the consumer. This will include most attributes of the physical tables.

5.2.2 Identifying Service Operations on the Business Objects

The service operations are the actions that can be performed on business objects, such as create and delete.

5.2.2.1 Types of Operations

Standard operations and Custom operations are the two types of service operations that are supported by ADF Business Components service.

Standard Operations

The primary purpose of standard service operations is to locate a business object and handle its persistence. This includes storage, manipulation and retrieval of data, locking, transaction management, business rule validation, and bulk processing. ADF Business Components auto-generates the following groups of standard service operations:

  • CRUD (Create, Read, Update, Delete) Operations:

    • get<businessObjectName>: get a single business object by primary key.

    • create<businessObjectName>: create a single business object.

    • update<businessObjectName>: update a single business object.

    • delete<businessObjectName>: delete a single business object by primary key.

    • merge<businessObjectName>: update a business object if exists, otherwise create new one.

  • Find Operation:

    • find<businessObjectName>: find and return a list of business objects by find criteria.

  • Bulk Processing Operations:

    • process<businessObjectName>: process a list of business objects via a CRUD command.

    • processCS<businessObjectName>: process a list of business objects via a change summary.

  • Control Hints Operations:

    • List<AttrCtrlHints>

    • getDfltCtrlHints(String viewName, String localeName): takes the view object name and a locale and returns the base UI hints for that locale.

Custom Operations

Custom service operations encapsulate complex business rules and may coordinate execution of two or more data-centric operations within one atomic transaction.

5.2.2.2 Identifying Operations

All core business functions should be exposed in services. When developing a list of business object operations, consider the entire application life cycle from creation to deletion. Also, consider potential use cases that are required by others. Typically, the functionality exposed from UI should be achievable from services. For example, the following list includes many of the operations associated with requisitions and purchase orders:

  • Operations associated with Requisitions:

    • create

    • merge

    • update

    • delete requisition

    • delete requisition line

    • delete requisition distribution

    • copy requisition

    • get requisition

    • cancel

    • approve (including all approval states such as approve, reject, and pre-approve)

    • view approval history

  • Operations associated with Purchase Order:

    • create

    • delete purchase order

    • delete purchase order line

    • delete purchase order shipment

    • delete purchase order distribution

    • copy purchase order

    • merge

    • update (change)

    • acknowledge

    • get purchase order

    • cancel purchase order

    • cancel purchase order line

    • approve (including all approval states)

    • view approval history

    • close

Typically, you should include all the standard operations, although the delete operation should only be included if supported by the business object.

Custom operations must be coarse-grained and must reflect a business task to be carried out. activateCustomerParty, suspendOrder, and closeServiceRequest are some examples.

5.2.2.3 Defining Service Operations - General Guidelines

There are general guidelines you must follow when defining service operations.

Be generic where it makes sense

Since most Oracle Fusion services serve multiple use cases as listed in Section 5.2.2.2, services should not be designed narrowly for only one use case at the exclusion of others. Instead, services should be designed from the start to be general purpose and contain APIs that can serve the widest use cases. This is especially important to consider for common business functions that are initially required for the UI or to meet enhancement requests from other products. It should be the conceptual essence of the use case that drives the interface, not the fine-grained specifics of one consumer. A consumer can be seen as a representative of a specific use case, but the provider should always apply well-measured foresight when defining the interface details. Creating general purpose APIs from the beginning will:

  • Prevent method explosion as other similar use cases are requested.

  • Increase reuse by multiple use case.

For example:

There is a requirement to return a person's name based on personId. To satisfy that requirement, you might want to create an operation such as:

public String getPersonName(Long personId)

However, it is likely that this service will soon be adopted by more consumers, which will require other attributes of a person. In this example, it makes sense to return the person object instead of just the name into the first specification of the service interface, such as:

public Person getPerson(Long personId)

Leverage standards wherever possible

When there is an existing industry standard that can be applied to your service, leveraging the standard is definitely recommended to avoid costly negotiation of proprietary interfaces.

However, in many situations either no standard exists or the standard does not optimally support your business need. In this case you should make the interface as generic as possible for your given group of consumers. This will make sure that the interface stays stable as more consumers adopt it, while being highly useful for your given business processes. With good strategic planning it is possible to define generic interfaces for a defined subset of stakeholders.

Service operation granularity

Because it is possible to call services across a network, the service operations should be generally coarse-grained. That is, a service operation should wrap a substantial body of application logic, delivering value that justifies the latency cost of a network request. Similarly, services should expose coarse-grained operations. Rather than expose many operations that each manipulate small amounts of state, services should expose fewer operations that allow a single request to perform a complete function.

Compensating service operations

For operations that involve data manipulation, a clear strategy for compensation must be defined. Services are frequently distributed remotely and there is no central transaction coordinator with sufficient control over all resources. This is inherent in Simple Object Access Protocol (SOAP), which is predominantly used in the web services space and therefore, a two-phase commit protocol cannot be enforced. Also, two-phase commit implies resource locking, which may lead to scalability and availability issues if locks are held for longer periods.

In order to allow for service operations to be undone, in certain business scenarios it may be possible to offer compensating service operations. These operations are used to revert the system back to the state before the original operation was invoked. Providers and consumers must agree on the conditions under which an original operation can be undone and what information is required to achieve the compensating effect.

In most cases, the decision to provide a compensating operation is primarily functional. It might technically be possible to delete an existing purchase order, but functionally it is only correct to cancel it once it has been submitted for approval. Not all operations should, by default, be paired with a compensating operation. Compensating operations should be provided only if the business process demands that the system can be rolled back into the original state.

If it is not possible to provide a compensating operation, you should strive to make the service operations truly accomplishable (idempotent). Making the operations idempotent means that the operation can be invoked multiple times with the same payload but still the result will be the same with no undesirable effects. If neither providing compensating service operations nor making the service operations idempotent is feasible, processes should be designed in such a way that the service operation would never be called more than once under any circumstances for a specific request.

Service operation parameters

Each service operation can have zero or more parameters. Each parameter can be a primitive type (String, Date, and so on), a complex type represented as a Service Data Object (SDO), or a List of a primitive type or SDO. Complex types can in turn contain nested complex types.

  • Long Parameter List or Complex Types?

    You should consider using complex types in a service operation instead of using a long list of individual parameters unless the parameter list can be reduced to a short list of simple types (3-5).

    For example:

    A service operation updatePerson() takes a compound complex type of Person, which includes several individual attributes such as BirthDate, a collection of PersonName, and a collection of PersonAddress, and so on. The reasons are:

    • Taking a list of individual parameters leads to a not so clean operation signature:

      Example 5-1 Service Operation on a List of Parameters

      void updatePerson (Date BirthDate, PersonName[] Names, PersonAddress[] Addresses);
      
    • Adding an optional attribute on a complex type doesn't break compatibility, but adding a new parameter in a method does.

    • In the updatePerson example, if the person's email address needs to be updated, the operation that takes a Person can stay unchanged.

  • Complex Types or Primary Keys?

    As an alternative to complex types, business object Primary Keys can be used in operation signatures in certain cases.

    Auto-generated data-centric standard operations, such as create, update, delete, merge, and Bulk Processing take complex business object types as parameters. Auto-generated get() takes primary keys.

    Custom methods may take business object primary keys or developer key as parameters, when the key attribute is used to look up the business object, such as terminateEmployee. Complex business object documents should be passed primarily to the data-centric custom methods: validatePerson(), promoteEmployee(), formatPersonAddress(), and so on.

5.2.3 How to Identify Services

A service is a grouping of operations. Often this grouping is by the business object it maintains, which is especially true for the CRUD operations. In most cases, one service per business object provides a more manageable hierarchy. For example, the business object Person could be offering all operations that can be performed on it as a service called PersonService.

After you have identified your services and what business object(s) they include, the list(s) of the corresponding operations that were identified in Section 5.2.2.2 provide the list of candidate methods for each service.

Services from other products that may compliment this list are not included. For example, a SupplierService and InvoiceService provide detailed information about suppliers and invoices respectively. The procurement services should identify only who the supplier is in various transactions and provide information on procurement-specific supplier data such as, supplier price, quality and on-time delivery performance. It should not provide core supplier operations like creating, updating, deleting, and so on because that is the responsibility of the SupplierService.

It's important that the Oracle Fusion services compliment one other. Therefore, once you've identified your working list of services, coordinate with related products to ensure that you have not duplicated efforts or created confusing and conflicting APIs. Also, communicate any expectations that you have of their services.

5.2.4 How to Define Service Exceptions and Information

Services might throw exceptions or warnings when there is issue of processing inbound request. Sometimes it is desirable to return informational messages along with the response. It is an important step to define when to return exceptions, warnings, and informational messages, and what messages should be returned. This is especially true for the bulk processing operations with partial failure.

5.2.4.1 Defining Service Exceptions

Once the required criteria for successful execution of a service operation is agreed upon, all stakeholders must then define a complete set of error conditions. You must define which exceptions can happen and which information should be reported back to the consumer due to an exception. Exception processing should be consistently implemented across all operations in the application. If one operation throws an exception while another returns an empty collection, the consumers perceive the services as unstable and unpredictable. The reported exception should contain as much information as possible so that the consumers can pinpoint the problem easily.

All service operations are delegated to the underlying ADF Business Components objects and their methods. As a service provider, you just need to implement your validation logic and business rules in your server side objects, define appropriate error messages declaratively, or throw appropriate JboExceptions programmatically.

Oracle ADF has one generic exception or fault to handle all ADF Business Components exceptions. Whenever an exception is thrown from the underlying ADF Business Components object for one of the service standard or custom methods, the exception is thrown as a Service Exception, which contains all of the information available from the original thrown exception. This also includes support for bundled exceptions.

5.2.4.2 Defining Partial Failure and Bulk Processing

Services can support partial failure during bulk processing of data, which can be very useful. For example, if the client loads a large amount of data using batch load applications, the occurrence of one or more failures does not prevent the continued posting of other unrelated data.

During design of your services, you need to decide whether partial failure should be enabled for a business object including details. For example, a purchase order business object includes a header, lines for each header, and shipments for each line. The partial failure switch is set on each level including header, line, and shipment. Usually the top level object should allow partial failure, but the decision on the detail level depends on whether it make sense to simply skip that object if it fails. You must ask yourself the question: "Does it make sense to still post the other lines and the header if one line fails?" In some cases, you may need to preserve the integrity of the business object and not allow the object to be posted with partially populated children.

Caution:

The partial failure mode is only used in the processXXX API and this API also uses a runtime partial failure flag in the ProcessControl parameter. This means the partial failure feature is only enabled when both the design time flag and the runtime flag are enabled.

5.2.4.3 Defining Informational Messages

Informational messages are not exceptions and won't affect the current transaction. However, these messages may be useful to the clients. For example, you may want to know when the system automatically transfers money from your saving account to your checking account because there may not enough funds in your checking account when your check is cashed out.

The service provider needs to define a complete list of informational messages as well as the conditions that these messages should be returned.

5.3 Developing Services

After you design the service interface, you now must implement those services.

Note:

This is from the service provider perspective.

For more information see "Implementing Business Services with Application Modules" in Oracle Fusion Middleware Fusion Developer's Guide for Oracle Application Development Framework (Oracle Fusion Applications Edition).

5.3.1 How to Create Service Data Objects

In Oracle Fusion, service data objects (SDOs) are used to expose business objects in services. SDO is an industry standard, and it provides a unifying API and architecture that allows SOA applications handle data from heterogeneous sources, including relational databases, Web services, and enterprise information systems. Oracle has implemented it based on the SDO specification.

Each SDO must be backed by a view object. Hence, you don't create the standalone SDO. Instead, you always start with a view object, and service-enable the view object using the "Generate Service Data Object Class" option in the view object wizard's Java panel to create SDO.

This implies that you might need to create transient view object when there is no corresponding entity object. For example, you want to use a SDO to capture parameters when number of parameters is big.

5.3.1.1 SDO Attributes

When you create the view object, make sure the attribute are properly defined. See Section 5.2.1.1 for information about what attributes should be included in SDO. You can hide view object attribute from SDO if you need to by navigating to the attribute editor and de-selecting the SDO Property checkbox.

Note:

If a view object extends another one, then the subtype SDO cannot hide or unhide an attribute that is defined in the base SDO.

The SDO attribute name is derived from the view object attribute name, and you cannot override it.

Attribute Type Consistency

In general, the SDO attribute type is defaulted from the view object attribute, and you can just use the default value. However, there are several exceptions.

  • Boolean type

    A SDO attribute in boolean domain (only have true/false value) must be of Boolean type.

    Oracle ADF provides a feature that maps between the view object attribute String type and the SDO attribute Boolean type. This feature will allow you to continue using String in the view object attribute, but use Boolean in SDO.

    To expose a Boolean domain view object attribute of String type as Boolean type in SDO, do the following:

    1. Determine the possible values of the attribute. Since it is of Boolean domain, there are only two possible values: Y/N, T/F, or 1/0.

    2. If needed, create a property set. This property set is used to indicate how Oracle ADF should convert between String and Boolean. Oracle ADF provides property sets for Y/N or T/F or 1/0. If the possible values are not Y/N or T/F or 1/0, then you will need to create a property set. Navigate to the New Object Gallery, and select Property Sets under ADF Business Components.

    3. Navigate to the Source view and add the following element as a child of the Domain element:

      <Domain>
        ……
        <Properties>
          <SchemaBasedProperties>
            <BooleanValueMapping>
              <ValueMapping JavaStringValue="true" StorageValue="A"/>
              <ValueMapping JavaStringValue="false" StorageValue="I"/>
            </BooleanValueMapping>
          </SchemaBasedProperties>
        </Properties>
      </Domain>
      

      This example assumes that the possible values are "A" and "I", where "A" means true and "I" means false. You should replace these values with your own.

    4. Specify the property set for the attribute. If the view object attribute is entity-object based, then go to the entity object. Otherwise, go to the view object. Navigate to the Source view and, in the corresponding Attribute or ViewAttribute section, add the TypeValueMapPropertySet attribute. For example:

      <Attribute
          Name="…Flag"
          ……      TypeValueMapPropertySet="oracle.jbo.valuemaps.BooleanYNPropertySet">
          ……
        </Attribute>
      

      If your attribute is of Y/N domain, then use oracle.jbo.valuemaps.BooleanYNPropertySet as the property set. If it is of T/F domain, then use oracle.jbo.valuemaps.BooleanTFPropertySet. If it is of 1/0 domain, then use oracle.jbo.valuemaps.Boolean10PropertySet. Otherwise, you will need to use your own property set, which you created in the previous step.

    5. Navigate to View Object wizard > Java > Generate Service Data Object Class and generate or regenerate the SDO.

  • AmountType

    An SDO attribute storing a price or amount could use the AmountType datatype. Examples are 100 USD or 35.3 RMB. A number and a currency code are always present in this type of attribute. AmountType is a complex type and defined as:

    <xsd:complexType name="AmountType">
      <xsd:simpleContent>
        <xsd:extension base="xsd:decimal">
          <xsd:attribute name="currencyCode" type="xsd:normalizedString" 
           use="optional"/>
        </xsd:extension>
      </xsd:simpleContent>
    </xsd:complexType>
    

    Therefore, a SalePrice attribute of 100 USD will be represented in xml as:

    <SalePrice currencyCode="USD">100</SalePrice>
    

    Two attributes in view objects need to be present in order to use the AmountType: One for the number/amount itself, and the other for the currency code. To use the AmountType, do the following:

    1. Navigate to View Object wizard >Attributes and select the specified Price or Amount attribute. Then, click Edit.

    2. In the View Attribute panel, select AmountType as the XSD Type.

    3. In the corresponding CurrencyCode field, choose the attribute that is used to determine the currency code. If the currency is not defined in the current view object and derived from another place, first create a transient attribute and add logic to populate the transient attribute value, for example, via a Groovy expression. For the transient currency code, override the setter method of the ViewRowImpl class, and validate that the new value is the same as the source attribute. For example, if you have a currency code defined at the parent level, then the transient currency code attribute at the child level should be always the same as the parent level currency code value.

    4. Keep the currency code in the SDO payload. In another words, always expose the currency code attribute in SDO, and don't disable "SDO Property". This will allow the consumer to change the currency code even if the price/amount is not present.

      During runtime, if the payload contains conflicting currency codes, then an exception will be thrown from Oracle ADF. For example, an exception will be thrown if the service payload is something like the following:

      <SalePrice currencyCode="USD">100</SalePrice>
      <ListPrice currencyCode="RMB">100</SalePrice>
      

      Instead, both SalePrice and ListPrice should contain same value for the currentCode attribute:

      <SalePrice currencyCode="USD">100</SalePrice>
      <ListPrice currencyCode="USD">100</SalePrice>
      

      Or the currencyCode is specified in one element and omitted from the other:

      <SalePrice currencyCode="USD">100</SalePrice>
      <ListPrice>100</SalePrice>
      
  • MeasureType

    An SDO attribute storing a quantity could use the MeasureType data type. Examples of quantities are: 10 meters or 105.3 pounds. Two things are always associated with a quantity: A number and a unit of measure. MeasureType is a complex type and defined as the following:

    <xsd:complexType name="MeasureType">
      <xsd:simpleContent>
        <xsd:extension base="xsd:decimal">
          <xsd:attribute name="unitCode" type="xsd:normalizedString" use="optional"/>
        </xsd:extension>
      </xsd:simpleContent>
    </xsd:complexType>
    

    Therefore, a Length attribute of 10 meters will be represented in xml as:

    <Length unitCode="meter">10</Length>
    

    In a view object, there are two attributes related to this one quantity: The number attribute and the unit of measure attribute. To define the attribute as MeasureType, do the following:

    1. Navigate to View Object wizard >Attributes and select the specified Quantity attribute. Then, click Edit.

    2. In the View Attribute panel, select MeasureType as the XSD Type.

    3. In the corresponding unitCode field, choose the attribute that is used to determine the unit of measure. If the unit of measure is derived from another place, create a transient attribute and add logic to populate the transient attribute value, for example, via a Groovy expression. For the transient currency code, override the setter method of the ViewRowImpl class, and validate that the new value is the same as the source attribute.

    4. Keep the unit of measure attribute in the SDO. In another words, always expose the unit of measure attribute in SDO, and don't disable "SDO Property". This will allow consumers to change the unit of measure even if the quantity itself is not present.

    As w with AmountType, an exception is thrown by Oracle ADF if the payload contains conflicting unit codes.

5.3.1.2 Parent-Child Relationships

For parent-child relationships, you should define two view objects, one for the parent and one for the child, and then define a view link between them. You must also have the destination accessor generated so that the service framework is unable to query or post the child along with the parent.

For composite object, you should create a composite association between the parent entity object and the child entity object, and base the view link on the association. However, in cases where composite associations cannot be defined you must add a custom property, SERVICE_PROCESS_CHILDREN=true, to the entity association or view link. This allows for the child objects to be processed along with the parent object (in createXXX, updateXXX, mergeXXX, deleteXXX, and processXXX). Reasons for cases where composite associations cannot be defined include:

  • A child has multiple parents but the relationship is really composite.

When there is an entity association and the association has the destination accessor generated, then you should add the custom property in the association. When an association doesn't exist such as flexfield or the association doesn't have the destination accessor generated, then you must add the same property to the view link.

5.3.1.3 Enabling Partial Failure

The default setting for partial failure is not enabled. To enable partial failure, add the PARTIAL_FAILURE_ALLOWED custom property on the view object and set the value to true.

To determine if you should enable partial failure, see Section 5.2.4.2, "Defining Partial Failure and Bulk Processing."

5.3.1.4 Enabling Support Warnings

There is a design time flag to indicate whether the informational messages are enabled or not for each view object and service data object. The default setting for this flag is off, and you need to go to the view object editor's Java tab and select the Support Warnings field.

Note:

Signatures of the service operations that ADF Business Components generate vary depending on this Support Warnings flag. If you change this flag in a future release, your service will no longer be backward compatible. In addition, when partial failure is on, the exceptions are not thrown from the service invocation. Instead, the exceptions are reported as warnings, and the caller can only receive these warnings if the Support Warnings flag on the service view object is turned on. Therefore, you must turn on the Support Warnings flag for the top-level service data objects that are exposed directly in the process methods.

For the purchase order header, line, and shipment example, if your service includes the processPurchaseOrders API that takes a list of purchase order headers, then you must enable Support Warnings in the purchase order header view object. If your service also includes the processLines API that takes a list of purchase order lines, then you also need to enable Support Warnings in the line view object. For the other detail level service data objects, you should take a more proactive approach and define your business object as supporting informational messages if you think you will need this feature in the future.

5.3.1.5 Defining a List of Values (LOV) to Resolve Foreign Key ID

In many cases, it is desirable to include developer key attributes for foreign keys. For example, an employee service is used to process employees, and the employee has a department associated with it. The department has a DepartmentId (surrogate primary key) attribute and a DepartmentName (developer key) attribute.

When the consumer constructs the inbound payload, the consumer could choose to provide DepartmentId or DepartmentName or both. If only DepartmentName is provided, then logic needs to exist to resolve DepartmentId based on the name. If both name and id are provided, then validation needs to be provided to make sure the two matches. An LOV can be defined to resolve foreign key ID based on foreign alternate key attributes.

An LOV is defined at attribute level, but it can be configured to be driven by multiple attributes. When an LOV is driven by multiple attributes, the LOV query will use all the driven attributes in the where clause. Service framework doesn't call individual setters, instead, it calls setAttributeValues() method in the ViewRowImpl class, which takes a list of attribute names and a second list of attribute values. LOVs will be fired after the attribute values are populated. So, if a foreign key has a composite alternate key, it will still work since the LOV fires after all the alternate key attributes are set.

When an attribute from a reference entity object is included in a view object, the attribute is read-only. However, if there is an LOV defined on the attribute, then the attribute is updateable in the sense that the attribute value can be used to drive the LOV.

The following are several scenarios and the steps you must perform for each of them.

Scenario 1

Single attribute Foreign Key and single attribute Alternate Key (Example: PersonVO has DeptId and Dname. DeptId is the foreign key ID, and Dname is the foreign alternate key).

An LOV should be defined on the alternate key attribute, with the foreign key ID as a derived attribute.

  1. Create PersonVO based on PersonEO and a reference DeptEO. The foreign alternate key (Dname) from the reference EO is included in the PersonVO.

  2. Define the LOV view object (DeptVVO).

  3. Define a view accessor on PersonEO/PersonVO pointing to DeptVVO.

  4. Define an LOV on the foreign alternate key (Dname) using the above view accessor, and configure the LOV to populate the foreign key ID (DeptId) as the derived attribute.

Scenario 2

Single attribute Foreign Key and multiple attribute Alternate Key (Example: PersonVO has OrganizationId as foreign key id, and OrganizationName+BusinessGroupName as the composite alternate key).

Each alternate key attribute needs to have an LOV defined, and each LOV should have all the alternate key attributes as the driving attribute and the foreign key ID as the derived attribute.

  1. Create PersonVO based on PersonEO and a reference OrganizationEO and another reference BusinessGroupEO. The foreign alternate key (OrganizationName and BusinessGroupName) from the reference entity objects are included in the PersonVO.

  2. Define the LOV view object (OrganizationVVO).

  3. Define a view accessor on PersonEO/PersonVO pointing to OrganizationVVO.

  4. Define an LOV on each of the foreign alternate key attributes (OrganizationName and BusinessGroupName) using the above view accessor, and configure the LOV to populate the foreign key ID (OrganizationId) as a derived attribute.

  5. Modify PersonVO.xml to make the LOVs driven by all the foreign alternate key attributes. For example:

    <ListBinding
        Name="LOV_OrganizationName"
        ListVOName="OrganizationVA"
        ListRangeSize="-1"
        NullValueFlag="none"
        NullValueId="LOV_OrganizationName_LOVUIHints_NullValueId"
        MRUCount="0">
        <AttrArray Name="AttrNames">
          <Item Value="OrganizationName"/>
          <Item Value="BusinessGroupName"/>
        </AttrArray>
        <AttrArray Name="DerivedAttrNames">
          <Item Value="OrganizationId"/>
        </AttrArray>
        <AttrArray Name="ListAttrNames">
          <Item Value="OrganizationName"/>
          <Item Value="BusinessGroupName"/>
          <Item Value="OrganizationId"/>
        </AttrArray>
    

Scenario 3

Single attribute Foreign Key and multiple attribute Alternate Key and one of the alternate key attribute is another foreign key (Example: PersonVO has BirthOfCountry as foreign key id, and BirthOfCity as another foreign key ID. BirthOfCity has BirthOfCountry+CityName as a composite alternate key).

The first foreign key (BirthOfCountry) needs to be resolved first, either based on #1 or #2. Then the second alternate key should filter by the first alternate).

  1. Create PersonVO based on PersonEO and a reference EO CountryEO and another reference EO CityEO. CountryName from CountryEO and CityName from CityEO should be included. CityName should be listed after CountryName in the PersonVO.

  2. Define an LOV view object based on CountryEO.

  3. Define an LOV view object based on CityEO. Define a view criteria to filter by CountryId.

  4. Define a view accessor on PersonEO/PersonVO pointing to CountryVVO.

  5. Define a view accessor on PersonEO/PersonVO pointing to CityVVO, and bind CountryId to BirthOfCountry.

  6. Define an LOV on CountryName using CountryVVO view accessor, with BirthOfCountry as a derived attribute from CountryId from CountryVVO.

  7. Define an LOV on CityName using CityVVO view accessor, with BirthOfCity as a derived attribute from CityId from CityVVO.

Scenario 4

Composite foreign key.

Each foreign key ID will be dealt with individually. For example, the foreign key id is OrgId+SourceId, then orgId and SourceId should be resolved based on solution in #1 or #2 or #3 separately. Then a validator needs to be defined to make sure combination of OrgId and SourceId is valid. This has the assumption that each individual attribute are a primary key itself.

5.3.2 How to Create Services

The service interface is generated from an Oracle ADF application module. Go to the Service Interface tab in the Application Module wizard to expose the service interface for an application module..

5.3.2.1 What You May Need to Know About Design Time

This section discusses what happens during design time with application modules and the runtime object.

No Service Data Object in the Application Module

The custom methods in the Application Module do not take Data Object or a Data Object list as parameters. Instead, the Application Module's custom methods take ViewRowImpl/AttributeList or a list of ViewRowImpl/AttributeList as parameters. When you publish these methods in the service interface, ADF Business Components service will convert these to Data Object or a list of Data Object in the service interface during design time, and then performs conversion between Data Object and ViewRowImpl/AttributeList during runtime.

Return Object

The informational messages (and warnings) are reported as part of the return object. ADF Business Components generates appropriate wrappers as the return objects when necessary, and the wrappers contain the actual method return as well as the informational messages. Table 5-1 lists some examples:

Table 5-1 Return Objects Examples

Operation without Informational Messages (Support Warnings Flag is off) Operation with Informational Messages (Support Warnings Flag is on) Comments

List<Person> processPerson(String op, List<Person> persons, ProcessControl ctrl)

PersonResult processPerson(String op, List<Person> persons, ProcessControl ctrl)

PersonResult contains a list of Persons, and a list of ServiceMessages.

Person createPerson(Person person)

PersonResult createPerson(Person person)

The list of Person in PersonResult should contain only one element.

void terminateEmployee(BigDecimal empId)

ServiceMessage terminateEmployee(BigDecimal empId)

 

String getApplicationName(BigDecimal applicationId)

StringResult getApplicationName(BigDecimal applicationId)

The StringResult contains a String and a list of ServiceMessages.


If the Support Warnings design time flag is off, no informational messages are returned (the first column in the above table). If the flag is on (the second column in the above table), then:

  • getXXX returns the original object

  • create, update, mergeXXX, findXXX, and processXXX returns the wrapper object that contains a list of the original object and a list of information messages

  • deleteXXX returns the informational message

  • Each custom method can be configured individually about whether to return informational messages

    Note:

    Note that ServiceMessage is created by the framework based on the warning or errors thrown during service execution. When you develop your service, you don't throw ServiceMessage. Instead, you throw JboException or ApplcoreException. See Section 5.2.4 for information about error and informational messages.

Find Operation

When you include the standard operations in a service interface, you can enable the generic find operation, or select a view criteria and expose a find operation that utilizes the view criteria.

Oracle ADF has a default list of operators (such as =, contains, and so on) that can be used in the find operation. You can enable more custom operators or disable an operator in the default list.

5.3.3 How to Generate Synchronous and Asynchronous Service Methods

Each service method can be exposed as both synchronous version and asynchronous version.

To generate synchronous and asynchronous service methods:

  1. Go to the Application Module Design Time wizard.

  2. Choose the Service Interface tab.

  3. Choose the Service Interface category. See Figure 5-2.

    Figure 5-2 Edit Service Interface Dialog

    Edit Service Interface dialog
  4. Select Generate Asynchronous Web Service Methods. Click OK.

  5. Save your changes.

5.3.4 How to Expose Flexfields

Flexfields are used as an extension points. If your object has a flexfield defined, then you might want to consider including the flexfield in the service interface so that your customer can utilize flexfields through web service.

See Section 22.13, "Publishing Descriptive Flexfields as Web Services" and Section 24.5.4, "How to Publish Key Flexfield Application Modules as Web Services" for information about how to expose the flexfields in a service interface.

5.3.5 How to Enable Security

You need to enable security on your service to make sure only the granted people can invoke the service.

5.3.5.1 Authentication

An authentication policy determines how the caller proves its identity. A simple case could be user name and a password in clear text, which is simple but not very secure. Or a token can be generated by an identity provider and then passed to the service provider.

See Chapter 50, "Securing Web Services Use Cases" for information about the different authentication policies supported. Once a security policy is chosen, you need to add it as an annotation in your xxxServiceImpl class. For example:

@SecurityPolicy( { "oracle/wss11_saml_or_username_token_with_message_protection_
  service_policy"})
@CallbackSecurityPolicy("oracle/wss11_saml_token_with_message_protection_client_
  policy")

5.3.5.2 Authorization

Authorization determines who can invoke a service operation. To enable authorization, do the following:

  1. Add ServicePermissionCheckInterceptor as one of the EJB interceptors in your xxxServiceImpl class. The result will be similar to this:

    @Interceptors({ServiceContextInterceptor.class, 
    ServicePermissionCheckInterceptor.class})        
    

    Note:

    ADF Business Components service is implemented with EJB, and the EJB interceptor is used here to authorize before any service operation can be invoked.

  2. Grant access to the service operations to desired roles in jazn-data.xml:

    1. Add a web service resource type.

      Navigate to Application Resources >Descriptors/META-INF/jazn-data.xml, click the Resource Grants tab, and then add a new resource type. This is shown in Figure 5-3.

      Figure 5-3 Create Resource Type

      Create Resource Type

      The following xml snippet in jazn-data.xml will be generated:

      <resource-type>
        <name>WebserviceResourceType</name>
        <display-name>WebserviceResourceType</display-name>
        <description>Webservice Resource</description>
        <provider-name/>
        <matcher-class>oracle.wsm.security.WSFunctionPermission</matcher-class>
        <actions-delimiter>,</actions-delimiter>
        <actions>invoke</actions>
      </resource-type>
      
    2. Add a resource, as shown in Figure 5-4.

      – Add the privilege and specify which role can access the resource defined above.

      – Click Entitlement Grants in jazn-data.xml.

      – Create a new entitlement or edit an existing one.

      – Add the newly created resource.

      – Choose the action.

      Figure 5-4 Entitlement Grants

      Entitlement Grants
    3. Add JpsInterceptor in ejb-jar.xml.

      Note:

      In the previous steps, the authorization is granted to the application roles. The application roles are computed using JpsInterceptor. Without the JpsInterceptor setting in ejb-jar.xml, the application roles won't be computed and authorization will fail.

      – Navigate to service project > Application Sources > META-INF/ejb-jar.xml and add the following code snippet:

      <ejb-jar…>
      ……
      <interceptors>
          <interceptor>
            <interceptor-class>oracle.security.jps.ee.ejb.JpsInterceptor</interceptor-class>
            <env-entry>
              <env-entry-name>application.name</env-entry-name>
              <env-entry-type>java.lang.String</env-entry-type>
              <env-entry-value><application_name></env-entry-value>
              <injection-target>
                <injection-target-class>oracle.security.jps.ee.ejb.JpsInterceptor</injection-target-class>
                <injection-target-name>application_name</injection-target-name>
              </injection-target>
            </env-entry>
          </interceptor>
        </interceptors>
        <assembly-descriptor>
          <interceptor-binding>
            <ejb-name>*</ejb-name>
            <interceptor-class>oracle.security.jps.ee.ejb.JpsInterceptor</interceptor-class>
          </interceptor-binding>
        </assembly-descriptor>
      </ejb-jar>
      

      Replace application_name with your application name, which is defined under policy-store/applications/application/name element in your jazn-data.xml.

    4. Specify the applicationid in weblogic-application.xml:

      <application-param>
         <param-name>jps.policystore.applicationid</param-name>
         <param-value><application_name></param-value>
      </application-param>
      

      Replace application_name with the application name you used in Step c.

5.3.6 Using the Java Transaction API

Java Transaction API (JTA) is used to handle ADF Business Components transactions.

5.3.6.1 Data Source

JTA manages distributed transactions across multiple resources. However, the resources must participate in global transaction in order to be part of the JTA. All the ADFbc services must use ApplicationServiceDBDS as the data source, and must not use ApplicationDBDS. ApplicationDBDS doesn't participate global transaction. ApplicationServiceDBDS supports global transaction using Oracle WebLogic Server Emulate Two-Phase Commit emulation. (See "JDBC Data Source Transaction Options" in Oracle Fusion Middleware Configuring and Managing JDBC Data Sources for Oracle WebLogic Server for more information.)

To specify the data source used by the service, do the following:

  1. Click the Configuration tab of Application Module wizard, then choose the configuration with type "SI"., as shown in Figure 5-5

    Figure 5-5 Edit Business Components Configurations

    Edit Business Components Configurations
  2. Select the appropriate data source for your service.

    If you don't see the data source in the dropdown list, you will need to create the database connection first.

  3. Navigate to ejb-jar.xml and add ApplicationServiceDBDS as the resource-ref:

    <ejb-jar ……>
      <enterprise-beans>
        <session>
          <ejb-name>……</ejb-name>
          <resource-ref>
            <res-ref-name>jdbc/ApplicationDBDS</res-ref-name>
            <res-type>javax.sql.DataSource</res-type>
            <res-auth>Container</res-auth>
          </resource-ref>
          <resource-ref>
            <res-ref-name>jdbc/ApplicationServiceDBDS</res-ref-name>
            <res-type>javax.sql.DataSource</res-type>
            <res-auth>Container</res-auth>
          </resource-ref>
    
          ……
    

5.3.6.2 Transaction Attributes

ADF Business Components service is implemented as a stateless session bean. Transaction attributes can be specified at the class level or method level, and these attributes determine the JTA behavior.

5.3.7 Deploying Services

The service can be deployed to an integrated or standalone Oracle WebLogic Server service.

To deploy to an integrated Oracle WebLogic Server, right-click the xxxServiceImpl class, and then select Run from the context menu.

To deploy to the standalone Oracle WebLogic Server, create a Business Component Service Interface profile. The window is shown in

Figure 5-6 Create Deployment Profile

Create Deployment Profile

This profile is a compound profile and includes two child profiles: Common (JAR File) and MiddleTier (EJB Jar File). The Common includes the service interface This .jar file will be required by the consumer when the consumer uses ServiceFactory to invoke the service. (See Section 5.4.1.1, "Using Service Factory" for more information.) The MiddleTier .jar file contains the service implementation.MiddleTier profile has a dependency on the common profile.

Include the MideleTier profile in your application's ear profile (Ear Deployment Profile >Application Assembly) so that the service will be included in the application ear file. Do not include the Common profile in the ear profile's Application Assembly. The Common profile will be included via profile dependency.

Note:

Do not include connections.xml in the common .jar file. There cannot be more than one connections.xml in one application, otherwise whatever shows up first in the classpath will be picked up, and this one might not be the right one. To prevent that, the service interface common profile cannot include connections.xml.

5.3.7.1 Service Context Root

When the service is deployed, the service can be accessed through a service end point URL, such as http://localhost:7101/mycontext/MyService. Note that the context root of a service is defined at the service project (Project properties >Java EE Application > Java EE Web Context Root, shown in Figure 5-7).

Figure 5-7 Project Properties

Project Properties

5.3.8 Testing Services

You can test the service only after it has been deployed.

5.3.8.1 What to Test

An ADF Business Components service is based on ADF Business Components. These components should have been already tested with junit, such as the defaulting and validation logic, queries, and other business logic. One commonly asked question is: Why should I test my service since my underlying application module/view object has been tested already? The ADF Business Components service testing is not a duplicate test of the ADF Business Components testing. Instead, the focus should be on the service layer and what is not covered in the ADF Business Components testing.

Areas that are not covered in the ADF Business Components testing are the following:

  • Security: ADF Business Components service is authenticated and authorized at the service layer, not the business components object layer.

  • Exception: The exception will come back as soap fault instead of JboException. You should check it has all the details that you expected.

However, you do not need to repeat the tests that have been done in ADF Business Components testing:

  • Data security is defined at the business components object layer, so you do not need to repeat the data security testing at service layer.

  • You need to test each service method, but you do not need to test all permutations of inbound parameters. Therefore, you probably just need to test a success case and a couple of typical failure cases for each of your service methods.

The following is a list of items that you should validate during the service testing (both the synchronous and asynchronous versions).

  • If the service is running

  • If the service is responding within a reasonable amount time

  • For all service methods:

    • Successful cases: Verify the response. You might also want to do a query after a post to check the data is indeed committed.

    • Failure cases: Verify the fault comes back, and with the expected content.

    • Security: Test both authentication and authorization.

5.3.8.2 How to Test

For ad-hoc testing, use the browser test page, HTTP Analyzer, or Oracle Enterprise Manager.

  • Web service test page

    In a web browser, access the service endpoint URL. The service end point URL follows the format of http://host:port/context_root/service_name. When the service is deployed to integrated Oracle WebLogic Server, the service endpoint URL is printed out in the Oracle JDeveloper console.

    After you open up the web service test page, you can enter the inbound parameters from the UI and submit the service request. For the service that is secured with GPA, you will need to expand the WS-Security element, and provide the user name and password there. For service that is secured with oracle/wss11_saml_or_username_token_with_message_protection_service_policy, you cannot invoke it from the web service test page.

  • HTTP Analyzer

    You can access HTTP Analyzer within Oracle JDeveloper by navigating to Tools > HTTP Analyzer, and then clicking on "Create New Request". You then can follow the UI to invoke a service. Similarly, it doesn't really handle service secured with oracle/wss11_saml_or_username_token_with_message_protection_service_policy.

  • Oracle Enterprise Manager

    Use Oracle Enterprise Manager to invoke a service. See "Testing Web Services" in Oracle Fusion Middleware Security and Administrator's Guide for Web Services.

For unit testing, you should write unit test code. For more information about ApplicationTestFramework and how to create the tests, see http://globaldc.oracle.com/perl/twiki/view/FusionSharedTools/ApplicationsTestFramework.

5.4 Invoking Services

All ADF Business Components services have both synchronous and asynchronous versions for the same method. The service consumer must decide which version of the service method to use.

A service method can be invoked synchronously if all of the following conditions are met:

The consumer should consider invoking the method asynchronously if one of the following conditions is met:

5.4.1 How to Invoke a Synchronous Service

You can invoke a synchronous service using service factory, service-based entity object and view object, Java API for XML Web Services (JAX-WS) client, or from SOA.

5.4.1.1 Using Service Factory

If you need to invoke a synchronous service from a Java client, including an ADF Business Components component, UI, or Oracle Enterprise Scheduler, then using a service factory is recommended.

  • It is easier to write a service client using service factory than using JAX-WS.

  • The consumer side doesn't need to generate or maintain the source control of the proxy code.

  • If the service is co-located, the service invocation is more performant because it does not invoke XML serialization and de-serialization.

To to invoke a service using service factory, do the following:

  1. Identify the common.jar file provided by the service provider team.

    Service factory requires that the service interface common .jar file be in the classpath of the consumer. (See Section 5.3.7 for information service deployment.) Subsequently, the consumer needs to identify the name of the common .jar file first from the provider team, and include it in the classpath.

  2. Retrieve the service endpoint information from the connections.xml file.

    Service Factory can be used to invoke a service via Remote Method Invocation (RMI) or Simple Object Access Protocol (SOAP). RMI can only be used within one domain, for example, receivables application invokes a service deployed in ledger application. RMI cannot be used across an Oracle WebLogic Server domain due to security constraint. For the cross Oracle WebLogic Server domain service invocation, such as when receivables invokes a service hosted in Oracle Fusion Human Capital Management, SOAP must be used.

    Service Factory uses ADF Connection Architecture to retrieve the service endpoint information from connections.xml. It creates a dynamic proxy to the remote service using the designated protocol in connections.xml.

    The following is a sample entry in connections.xml using RMI:

    <Reference name="{http://xmlns.oracle.com/apps/sample/dtsvc/}WorkerService" className="oracle.jbo.client.svc.Service" xmlns="">
          <Factory className="oracle.jbo.client.svc.ServiceFactory"/>
          <RefAddresses>
             <StringRefAddr addrType="serviceInterfaceName">
                <Contents>oracle.apps.sample.dtsvc.WorkerService</Contents>
             </StringRefAddr>
             <StringRefAddr addrType="serviceEndpointProvider">
                <Contents>ADFBC</Contents>
             </StringRefAddr>
             <StringRefAddr addrType="jndiName">
                <Contents>WorkerServiceBean#oracle.apps.sample.dtsvc.WorkerService</Contents>
             </StringRefAddr>
             <StringRefAddr addrType="serviceSchemaName">
                <Contents>WorkerService.xsd</Contents>
             </StringRefAddr>
             <StringRefAddr addrType="serviceSchemaLocation">
                <Contents>oracle/apps/sample/dtsvc/</Contents>
             </StringRefAddr>
             <StringRefAddr addrType="jndiFactoryInitial">
                <Contents>weblogic.jndi.WLInitialContextFactory</Contents>
             </StringRefAddr>
             <StringRefAddr addrType="jndiProviderURL">
                <Contents>t3://localhost:7202</Contents>
             </StringRefAddr>
          </RefAddresses>
    </Reference>
    

    In the example, the serviceEndpointProvider is ADF Business Components, which indicates that it is a RMI invocation.

    You need to replace the code in bold with the service that you plan to invoke. Table 5-2 lists the appropriate values:

    Table 5-2 Service Property Values

    Property Name Property Value Example

    Reference/name

    The service qualified name. It follows the format of {<targetNamespace>}<name>. The targetNamespace and name can be found in the wsdl file of the service to be invoked.

    {http://xmlns.oracle.com/apps/sample/dtsvc/}WorkerService

    serviceIntefaceName

    This is the service interface name. You can get it from the service interface java class in the common .jar file.

    oracle.apps.sample.dtsvc.WorkerService

    jndiName

    This is the EJB bean name. ADF Business Components service is implemented by EJB. It follows the format of <ServiceName>Bean#<ServiceIntefaceName>

    WorkerServiceBean#oracle.apps.sample.
    dtsvc.WorkerService

    serviceSchemaName

    The ADF Business Components service always has a schema file generated for the service. It follows the format of <ServiceName>.xsd.

    WorkerService.xsd

    serviceShemaLocation

    The path of the service schema. It is basically the service interface package path.

    oracle/apps/sample/dtsvc/

    jndiProviderUrl

    The host name and port number where the service is deployed. This is not needed during design time, but apparently required during runtime. This value will be tokenized during packaging and be "replaced" with the server URL during provisioning.

    t3://localhost:7202


    For a SOAP invocation, two entries are required in connections.xml: a ServiceFactory entry and a web service connection entry. The first entry is very similar to the RMI entry except for the following:

    • The serviceEndPointProvider value is SOAP.

    • A webServiceConnectionName entry, which is used to link to the second entry, is included. That is, the value of webServiceConnectionName must be the name of the second connection.

    • There are no jndiFactoryInitial or jndiProviderUrl properties.

    Example 5-2 and Example 5-3 show the two entries in connections.xml using SOAP.

    Example 5-2 Entry 1

    <Reference name="{http://xmlns.oracle.com/apps/sample/hrService/}HrService" className="oracle.jbo.client.svc.Service" xmlns="">
          <Factory className="oracle.jbo.client.svc.ServiceFactory"/>
          <RefAddresses>
             <StringRefAddr addrType="serviceInterfaceName">
                <Contents>oracle.apps.sample.hrService.HrService</Contents>
             </StringRefAddr>
             <StringRefAddr addrType="serviceEndpointProvider">
                <Contents>SOAP</Contents>
             </StringRefAddr>
             <StringRefAddr addrType="webServiceConnectionName">
                <Contents>HrServiceConnection</Contents>
             </StringRefAddr>
             <StringRefAddr addrType="serviceSchemaName">
                <Contents>HrService.xsd</Contents>
             </StringRefAddr>
             <StringRefAddr addrType="serviceSchemaLocation">
                <Contents>oracle/apps/sample/hrService/</Contents>
             </StringRefAddr>
          </RefAddresses>
       </Reference>
    

    Example 5-3 Entry 2

    <Reference name="HrServiceConnection" className="oracle.adf.model.connection.webservice.impl.WebServiceConnectionImpl" xmlns="">
          <Factory className="oracle.adf.model.connection.webservice.api.WebServiceConnectionFactory"/>
          <RefAddresses>
             <XmlRefAddr addrType="WebServiceConnection">
                <Contents>
                   <wsconnection description="http://rws65094fwks:7202/MySampleSvc/HrService?WSDL" service="{http://xmlns.oracle.com/apps/sample/hrService/}HrService">
                   <model name="{http://xmlns.oracle.com/apps/sample/hrService/}HrService" xmlns="http://oracle.com/ws/model">
                      <service name="{http://xmlns.oracle.com/apps/sample/hrService/}HrService">
                         <port name="HrServiceSoapHttpPort" binding="{http://xmlns.oracle.com/apps/sample/hrService/}HrServiceSoapHttp" portType="http://xmlns.oracle.com/apps/sample/hrService/}HrService">
                            <policy-references xmlns="http://oracle.com/adf">
                               <policy-reference category="security" uri="oracle/wss11_saml_token_with_message_protection_client_policy" enabled="true" id="oracle/wss11_saml_token_with_message_protection_client_policy" xmlns=""/>
                            </policy-references>
                            <soap addressUrl="http://rws65094fwks:7202/MySampleSvc/HrService" xmlns="http://schemas.xmlsoap.org/wsdl/soap/"/>
                         </port>
                      </service>
                   </model>
                </wsconnection>
                </Contents>
             </XmlRefAddr>
          </RefAddresses>
       </Reference>
    

    The second connection is a standard web service connection. To generate the web service connection entry in connections.xml, do the following:

    1. Create a web service proxy. (See "Creating a Web Service Proxy Class to Programmatically Access the Service" in Oracle Fusion Middleware Fusion Developer's Guide for Oracle Application Development Framework (Oracle Fusion Applications Edition), for more information.)

    2. Create a web service connection. (See "How to Create a New Web Service Connection" in Oracle Fusion Middleware Fusion Developer's Guide for Oracle Application Development Framework (Oracle Fusion Applications Edition) for more information.)

    3. Remove the web service proxy code.

    Note:

    The connections.xml file in source control contains the concrete web service endpoint URL. This URL can be used for testing in integrated Oracle WebLogic Server. As part of the ear generation, these URLs will be looked up and replaced with abstract tokens. These tokens will again be replaced when application is installed at customer site with the customer's server URL.

    There is a ServiceRepository.xml file that contains the list of services produced by Oracle Fusion Applications. The token replacement during ear generation uses this file to look up and replace the concrete URL with abstract tokens.

  3. Secure the service.

    For RMI invocation, only identity propagation is supported. Subsequently, there is nothing more that you need or can configure.

    For SOAP invocation, remember that the client-side security policy is driven by the server-side security policy, and the client-side policy must match the server-side policy. In most cases, you can utilize GPA, which uses wss10_saml_token_client_policy and propagates the identity from the caller to the service. The following cases require the use of LPA:

    • The service is protected by message protection policy. Usually, this service is an external service that can be accessed outside the Fusion Applications firewall. In this case, you will need to add the security policy in the web service connection of the connections.xml. For example:

      <Reference name="HrServiceConnection" className="oracle.adf.model.connection.webservice.impl.WebServiceConnectionImpl" xmlns="">
        <Factory className="oracle.adf.model.connection.webservice.api.WebServiceConnectionFactory"/>
          <RefAddresses>
            <XmlRefAddr addrType="WebServiceConnection">
              <Contents>
                <wsconnection …… >
                  <model ……>
                    <service ……>
                      <port ……>
                        <policy-references xmlns="http://oracle.com/adf">
                          <policy-reference category="security" uri="oracle/wss11_saml_token_with_message_protection_client_policy" enabled="true" id="oracle/wss11_saml_token_with_message_protection_client_policy" xmlns=""/>
                          </policy-references>
                        <soap …… >
                        ……
      

      The following are the client-side policies that you can use to invoke a service protected by message protection policy:

      • wss_username_token_over_ssl_client_policy

      • wss_saml_token_bearer_over_ssl_client_policy

      • wss11_username_token_with_message_protection_client_policy

      • wss11_saml_token_with_message_protection_client_policy

      • wss_http_token_over_ssl_client_policy

    • Identity switch is required. If the service needs to be invoked with a different user (usually using appid), then you need to specify LPA:

      <port ……>
        <call-properties xmlns="http://oracle.com/adf">
          <call-property id="csf-key" xmlns="">
            <name>csf-key</name>
            <value>system1-key</value>
          </call-property>
        </call-properties>
        <policy-references xmlns="http://oracle.com/adf">
          <policy-reference category="security" uri="oracle/wss_username_token_client_policy" enabled="true" id="oracle oracle/wss_username_token_client_policy" xmlns=""/>
          </policy-references>
          ……
      </port>
      

      Replace system1-key with the csf-key holding the application identity user name and password required by the user case. The actual application identities and their corresponding csf-keys are centrally provided.

      If the service is protected by message protection policy, then use oracle/wss11_username_token_with_message_protection_client_policy.

  4. Invoke the service.

    Write your client code to invoke the service. For example,

    OrganizationService svc=    (OrganizationService)ServiceFactory.getServiceProxy(OrganizationService.NAME);
    DataFactory datafactory = ServiceFactory.getDataFactory(svc);
    List orgs = new ArrayList(2);
    Org org1 = (Org)datafactory.create(Org.class);
    org1.setOrgName("OrgName");
    org1.setName("TranslatedName");
    org1.setDescription("Your org Description"); //... and set more attributes
    orgs.add(org1);
    svc.processOrganizatiion("Merge", orgs, null);
    

    SDOs based on polymorphic view objects don't have Java classes generated. To create these SDOs, use the generic DataObject class:

    import commonj.sdo.DataObject;
     
    DataObject emp = datafactory.create("http://xmlns.oracle.com/apps/fooService/", "Bar");
    emp.set("Empno", new Long(8080));
    emp.set("Ename", "Oliver");
    emp.set("Job", "MANAGER");
    

    Note:

    You will need to add "BC4J Service Client" library to your client project.

  5. Perform runtime control for the query.

    The standard find method API provides control over the query behavior.

    • Partial Attribute

      By default, the find operation returns all the attributes including all details. When you only need some attributes, you should set the partial attributes on the FindCriteria parameter of the find method. Do this in the following situations:

      • SDO contains LOB, which can be very expensive to retrieve and transfer.

      • SDO contains details that are not needed, such as translations. Querying detail is also expensive.

        Note:

        The standard getXXX function doesn't take a FindCriteria, so this function always returns everything. You should use findXXX to trim your return attributes.

      The following example shows how to set the partial attributes to include only Dname, Loc from Dept, and exclude Empno from Emp:

      FindCriteria fc = (FindCriteria)datafactory.create(FindCriteria.class);
      List l = new ArrayList();
      l.add("Dname");
      l.add("Loc");
      l.add("Emp");
      fc.setFindAttribute(l);
      List cfcl = new ArrayList();
      ChildFindCriteria cfc = (ChildFindCriteria)datafactory.create(ChildFindCriteria.class);
      cfc.setChildAttrName("Emp");
      List cl = new ArrayList();
      cl.add("Empno");
      cfc.setFindAttribute(cl);
      cfc.setExcludeAttribute(true);
      cfcl.add(cfc);
      fc.setChildFindCriteria(cfcl);
      DeptResult res = svc.findDept(fc, null);
      

      The following example shows how to set the partial attributes to exclude PurchaseOrderLine from PurchaseOrder:

      FindCriteria fc = (FindCriteria)datafactory.create(FindCriteria.class);
      List l = new ArrayList();
      fc.setExcludeAttribute(true);
      l.add("PurchaseOrderLine");
      fc.setFindAttribute(l);
      PchaseOrderResult res = svc.findPurchaseOrder(fc, null);
      
    • Filter

      The find API allows you to specify the WHERE clause of your query. The WHERE clause can be set on any level of the SDO. The following example shows how to retrieve only the departments with a department number greater than 10 and child employees whose names start with "A":

      FindCriteria fc = (FindCriteria)datafactory.create(FindCriteria.class);
      //create the view criteria item
      List value = new ArrayList();
      value.add(new Integer(10));
      ViewCriteriaItem vci = (ViewCriteriaItem)datafactory.create(ViewCriteriaItem.class);
      vci.setValue(value);
      vci.setAttribute("Deptno");
      List<ViewCriteriaItem> items = new ArrayList(1);
      items.add(vci);
      //create view criteria row
      ViewCriteriaRow vcr = (ViewCriteriaRow) datafactory.create(ViewCriteriaRow.class);
      vcr.setItem(items);
      //create the view criteria
      List group = new ArrayList();
      group.add(vcr);
      ViewCriteria vc = (ViewCriteria)datafactory.create(ViewCriteria.class);
      vc.setGroup(group);
      //set filter
      fc.setFilter(vc);
      
      List cfcl = new ArrayList();
      //create the child find criteria
      ChildFindCriteria cfc = (ChildFindCriteria)datafactory.create(ChildFindCriteria.class);
      cfc.setChildAttrName("Emp");
      //create the child view criteira
      ViewCriteria cvc = (ViewCriteria)datafactory.create(ViewCriteria.class);
      cfc.setFilter(cvc);
      //create the view criteria item
      List cvalue = new ArrayList();
      cvalue.add("A%");
      ViewCriteriaItem cvci = (ViewCriteriaItem)datafactory.create(ViewCriteriaItem.class);
      cvci.setValue(value);
      cvci.setAttribute("Dname");
      cvci.setOperator("LIKE");
      List<ViewCriteriaItem> citems = new ArrayList(1);
      citems.add(cvci);
      //create child view criteria row
      ViewCriteriaRow cvcr = (ViewCriteriaRow) datafactory.create(ViewCriteriaRow.class);
      cvcr.setItem(citems);
      List cgroup = new ArrayList();
      cgroup.add(cvcr);
      cvc.setGroup(cgroup);
      
      DeptResult dres = svc.findDept(fc, null);
      

      You also can query the parents with the children that satisfy certain criteria. For example, use the following to retrieve the departments with employees whose salary is greater than $10,000:

      //create the view criteria item on the employees
      List nvalue = new ArrayList();
      nvalue.add(new BigDecimal(10000));
      ViewCriteriaItem nvci = (ViewCriteriaItem)datafactory.create(ViewCriteriaItem.class);
      nvci.setValue(nvalue);
      nvci.setAttribute("Salary");
      nvci.setOperation(">");
      List<ViewCriteriaItem> nitems = new ArrayList(1);
      nitems.add(nvci);
      //create view criteria row
      ViewCriteriaRow nvcr = (ViewCriteriaRow) datafactory.create(ViewCriteriaRow.class);
      nvcr.setItem(nitems);
      //create the nested view criteria
      List ngroup = new ArrayList();
      ngroup.add(nvcr);
      ViewCriteria nvc = (ViewCriteria)datafactory.create(ViewCriteria.class);
      nvc.setGroup(ngroup);
       
      //create the view criteria item on the department
      ViewCriteriaItem vci = (ViewCriteriaItem)datafactory.create(ViewCriteriaItem.class);
      vci.setAttribute("Emp");
      vci.setNested(nvc);
      List<ViewCriteriaItem> items = new ArrayList(1);
      items.add(vci);
      //create view criteria row
      ViewCriteriaRow vcr = (ViewCriteriaRow) datafactory.create(ViewCriteriaRow.class);
      vcr.setItem(items);
      //create view criteria on department
      ViewCriteria vc = (ViewCriteria)datafactory.create(ViewCriteria.class);
      List group = new ArrayList();
      group.add(vcr);
      vc.setGroup(group);
      //set filter
      FindCriteria fc = (FindCriteria)datafactory.create(FindCriteria.class);
      fc.setFilter(vc);
       
      DeptResult dres = svc.findDept(fc, null);
      
    • Paging

      If you know that your query might return a large amount of data, you should make multiple service invocations, and use FetchSize and FetchIndex to control the amount of the data that you want to retrieve.

      The following example shows how to retrieve only the second employee and the employee's department:

      FindCriteria fc = (FindCriteria)datafactory.create(FindCriteria.class);
       
      List cfcl = new ArrayList();
      ChildFindCriteria cfc = (ChildFindCriteria)datafactory.create(ChildFindCriteria.class);
      cfc.setChildAttrName("Emp");
      cfc.setFetchStart(1);
      cfc.setFetchSize(1);
      cfcl.add(cfc);
      fc.setChildFindCriteria(cfcl);
      DeptResult dres = svc.findDept(fc, null);
      
  6. Perform runtime control for post.

    The process API provides a bulk operation that can handle multiple SDOs simultaneously.

    • Return mode

      Use ReturnMode on ProcessControl to specify whether you want to return a list of SDOs with all attributes, or with primary key attributes, or return nothing in processXXX method. You should only return the primary key or nothing, unless you need the full SDO returned for further processing

    • Exception return mode

      When an error occurs, the returned error message can contain just the primary key of the failure SDO, or all attributes of the failure SDO. You can specify which behavior you want using the ExceptionReturnMode attribute of the ProcessControl parameter in the processXXX method.

    • Partial failure

      For bulk load, it often makes more sense to commit as many records as possible, and report the problematic data. By default, either all SDOs go through or none goes through in processXXX method. However, you can call setPartialFailureAllowed on ProcessControl and pass that ProcessControl to the processXXX method. This will turn on the partial failure feature.

  7. Handle exceptions.

    All ADF Business Components services throw ServiceException. ServiceException contains FaultInfo, and FaultInfo stores the error messages. (Based on JAX-WS 2.0, exceptions thrown from service operations need to have a property named "FaultInfo".)

    @WebFault(name="ServiceErrorMessage", targetNamespace="http://xmlns.oracle.com/adf/svc/errors/",
              faultBean="oracle.jbo.service.errors.ServiceErrorMessage")
    public class ServiceException extends RuntimeException
    {
       public ServiceErrorMessage getFaultInfo()...
       public void setFaultInfo(ServiceErrorMessage faultInfo)...
     
    }
    

    The ServiceErrorMessage can contain one or more child error messages, and each child can contain its own children. Basically it is a tree hierarchy. You could consider ServiceErrorMessage as a counterpart of the JboException at the service layer.

    The ServiceErrorMessage has a few subclasses including ServiceAttrValErrorMessage, ServiceRowValErrorMessage, and ServiceDMLErrorMessage Their counterparts in ADF Business Components are AttrValException, RowValException, and DMLException. Note that ServiceException is an Exception, but all other classes above including ServiceMessage, ServiceErrorMessage, ServiceAttrValErrorMessage, ServiceRowValErrorMessage, and ServiceDMLErrorMessage are not exceptions.

    As the consumer of a service, if you call a service and need to take different actions based on the exceptions, you might need to walk through the message tree structure.

    The following is a simple example that walks through the message and converts to JboException:

    public static RuntimeException convertServiceException(ServiceException ex) {
            return convertToRuntimeException(ex.getFaultInfo());
        }
     
        public static RuntimeException convertToRuntimeException(ServiceMessage svcMsg) {
            RuntimeException ret = null;
            String msgStr = svcMsg.getMessage();
            List d = svcMsg.getDetail();
            Throwable[] details = null;
            if(d != null && !d.isEmpty()) {
                details = new Throwable[d.size()];
                for(int i=0; i<d.size(); i++)
                {
                    Object obj = d.get(i);
                    if(obj instanceof ServiceMessage)
                        details[i] = (convertToRuntimeException((ServiceMessage)obj));
                    else if(obj instanceof Throwable)
                        details[i] = (Throwable)obj;
                    else
                        details[i] = new Throwable(obj.toString());
                }
            }
            if(svcMsg instanceof ServiceErrorMessage) {
                JboException ex = new JboException(msgStr);
                ex.setExceptions(details);
                ret = ex;
            }
            else {
                JboWarning w = new JboWarning(msgStr);
                w.setDetails(details);
                ret = w;
            }
            return ret;
        }
    
  8. Create informational messages.

    Informational messages are not exceptions, and won't stop service from committing the current transaction. However, these information might be very useful to the clients. For example, you might want to know when there is not enough money in your checking account and have the system automatically transfer some from your savings account.

    The service provides decides whether a service will possibly return informational messages or not. If so, the informational message will be part of the return, as shown below:

    public EmployeeResult processEmployee(String changeOperation,
                                              List<Employee> employee,
                                              ProcessControl processControl) throws ServiceException;
    public interface EmployeeResult extends oracle.jbo.common.service.types.MethodResult {
       public java.util.List getValue();
       public void setValue(java.util.List value);
    }
    

    As the service consumer, you should retrieve the informational message from the return and perform appropriate actions such as converting it to JboException and displaying it in the UI. An example might be:

    ProcessControl pc = (ProcessControl)datafactory.create(ProcessControl.class);
    pc.setPartialFailureAllowed(true);
    EmployeeResult res = svc.processEmployee("Create", list, pc);
    if(res != null) {
      List msgs = res.getMessage();
      if(svcMsgs != null && !svcMsgs.isEmpty()) {
        Exception[] exceptions = new Exception[svcMsgs.size()];
        for(int i=0; i<exceptions.length; i++)
          exceptions[i] = convertToRuntimeException(svcMsgs.get(i));
          JboException ex = new JboException(....., exceptions); //create a bundled exception with the service errors as detail
      }
      throw ex;
    }
    

For more information about invoking a service using service factory, see Chapter 41, "Synchronously Invoking an ADF Business Components Service from an Oracle ADF Application."

5.4.1.2 Using Service-Based Entity Object and View Object

When you need to work with output from a service in the format of an ADF Business Components component, such as rendering the data in a UI table or creating a view link to it, then you should consider using service-based entity objects and view objects.

For more information about working with data from a remote ADF Business Components service, see Chapter 39, "Working with Data from a Remote ADF Business Components Service."

5.4.1.3 Using the JAX-WS Client

Generally, you should not use JAX-WS or Java APIs for XML-Based Remote Procedure Call (JAX-RPC) client to access an ADF Business Components service. You can use JAX-WS to access BPEL or a third-party service.

See "Creating a Web Service Proxy Class to Programmatically Access the Service" in Oracle Fusion Middleware Fusion Developer's Guide for Oracle Application Development Framework (Oracle Fusion Applications Edition) for information about how to create a JAX-WS proxy.

In summary, you need to do the following

  • Create the JAX-WS proxy client.

    Navigate to New Gallery > Business Tier > Web Services > Web Service Proxy to create proxy clients.

    Important:

    Perform the steps as listed when using the web proxy wizard to create the JAX-WS proxy client, with the following exceptions:

    • At the "Select Web Service Description" screen, deselect the Copy WSDL into Project checkbox.

    • At the "Specify Default Mapping Options" screen, deselect the Generate As Async checkbox.

    For the proxy code, it is recommended to have proxy in the package structure, such as oracle.apps.<lbaTop>.<lbaCore>.<xyzService>.proxy. It is recommended to generate the type in the "type" subpackage.

  • Create a web service connection.

    The proxy generated in previous step hard coded the service end point. To externalize the end point to connections.xml, you need to right-click on the generated proxy object, and choose Create Web Service Connection. In the Edit Web Service Connection dialog, provide the appropriate information, but don't provide any value for user name or password.

    This will create a connection entry in connections.xml. This entry can be changed during deployment.

    You can attach different security policies in the cconnections.xml file, as described in the Section 5.4.1.1, "Using Service Factory."

  • Develop client-side code. For example:

    ADFContext aDFContext = ADFContext.getCurrent();
    WebServiceConnection con =  (WebServiceConnection)aDFContext.getConnectionsContext().lookup("YourConnectionNameHere");
    YourService svc = con.getJaxWSPort(YourService.class);
    //invoke your service below
    ……
    

5.4.1.4 Using SOA

When you invoke an ADF Business Components service from BPEL, you usually use the asynchronous version unless you are sure the service satisfies the synchronous invocation condition that was discussed previously.

For more information, see Part VI, "Common Service Use Cases and Design Patterns".

Caution:

Web Service data control is an anti-pattern that is not allowed. Instead, build ADF Business Components components and bind these components to the UI. If you are trying to invoke an ADF Business Components service, then you can use service-enabled entity objects and view objects.

For more information, see the "How to Create Service-Enabled Entity Objects and View Objects" section in the "Integrating Service-Enabled Application Modules" chapter of the Oracle Fusion Middleware Fusion Developer's Guide for Oracle Application Development Framework (Oracle Fusion Applications Edition).

PL/SQL calling Web Service is also an anti-pattern that is not allowed because of security issues.

5.4.2 How to Invoke an Asynchronous Service

In general, BPEL is used to invoke an asynchronous service. If you need to invoke an ADF Business Components service from Java that does not meet the synchronous condition, then you must use one of the following alternate approaches:

Asynchronous Invocation (The caller-side must wait for response)

  • If the caller is ADF UI: The UI must raise an event, which is received by a mediator. The mediator invokes a BPEL process that invokes the asynchronous service and then invokes a second service after receiving the callback. The second service is responsible for notifying the UI side that the process has completed and then the UI uses the Active Data Service to refresh the UI.

    For information about how to enable the UI for dynamic update via Active Data Service, see Chapter 42, "Implementing an Asynchronous Service Initiation with Dynamic UI Update."

  • If the caller is Oracle Enterprise Scheduler Service: Oracle Enterprise Scheduler Service Java Jobs can invoke the asynchronous service via a JAX-WS proxy, but must set the asynchronous callback service to that of the Oracle Enterprise Scheduler Service Web Service. During this time, the Job's status will be Running and when the asynchronous callback comes through the Oracle Enterprise Scheduler Service Web Service callback port, the Job code will be notified with the response and can Complete.

One-way Invocation (The caller fires and forgets)

  • The caller must raise an event, which is received by a mediator. The mediator invokes a BPEL process, which invokes the asynchronous service. A callback is received from the asynchronous service.