Demo Fiori Application with CAP Model
In this exercise you will learn how to work with SAP CAP Model to build Fiori application with define security in SAP Cloud Platform Cloud Foundry Environment. Following diagram display the architecture for the sample
Make sure to have the following:
- An SAP Cloud Platform (Cloud Foundry) account containing at least the services:
- SAP HANA Database (Standard or Enterprise)
- SAP HANA Schemas & HDI Containers (hdi-shared)
- Portal Service
- An SAP Cloud Platform (Neo) account that provides access to at least SAP Web IDE.
-
If your are unsure where to find the Web IDE URL, follow this tutorial.
-
Web IDE opens up and shows your workspace. The workspace is empty if you use it for the first time.
2.1. Click on Cloud Foundry
in the Workspace Preferences
-
In the field for the
API endpoint
select the the URL that matches your Cloud Foundry account (usually the first URL). If you are asked to logon, use your user/password. -
Same for the values for
Organization
andSpace
: chose the values matching to your account. -
Should the be an error on the page saying that the builder is outed, press the
Reinstall Builder
button. -
Click on the Save button, even if you haven't changed anything.
You will get a confirmation message:
Use WebIDE Template SAP Cloud Platform Business Application
to start the project structure
Check Option : Use HTML5 Application Repository
Check Option : Enable User Authentication (UAA)
Create service instance for XSUAA service manually from SCP Cockpit with name - FioriCAPApplication1-uaa
plan - application
and following security setting
{
"xsappname": "FioriCAPApplication1",
"tenant-mode": "dedicated",
"description": "Security Configuration for CAP application",
"scopes": [
{
"name": "$XSAPPNAME.demouser",
"description": "Demo Scope"
},
{
"name": "uaa.user",
"description": "UAA"
}
],
"role-templates": [
{
"name": "demouser",
"description": "Demo Role",
"scope-references": [
"$XSAPPNAME.demouser"
]
},
{
"name": "Token_Exchange",
"description": "Token_Exchange",
"scope-references": [
"uaa.user"
]
}
]
}
update Project file mta.yaml
resource FioriCAPApplication1-uaa
with existing service, created in previous step
From:
- name: FioriCAPApplication1-uaa
type: org.cloudfoundry.managed-service
parameters:
service-plan: application
service: xsuaa
config:
xsappname: FioriCAPApplication1-${space}
tenant-mode: dedicated
to:
- name: FioriCAPApplication1-uaa
type: org.cloudfoundry.existing-service
parameters:
service-name: FioriCAPApplication1-uaa
Build CDS
from Project level and Build
DB module.
Test your srv
module using WebIDE testing tools.
Add HTML Module in project with List Report HTML5 Template
and bind with existing CDS OData V2 Service in project
Tempalte added additional XSUAA Resource in mta.yaml
file which need to replace with already created one FioriCAPApplication1-uaa
.
checked the template code for new module, you find that now HTML5 module is using HTML5 repository service to host the application from central place. Details can be found in SAP Blog.
In this example we have use Fiori Element to build UI, these Fiori element can be added via OData annotation by extending the exsiting cds model. Added new file cat-service-fiori.cds
inside srv module
using CatalogService from '../srv/cat-service';
annotate CatalogService.Books with {
ID
@Common.Label : 'Id';
title
@Common.Label : 'Title';
stock
@Common.Label : 'Stock';
author
@Common.Text: "author/name"
@Common.Label : 'Author'
@sap.value.list: 'fixed-values'
@Common.ValueList: {
CollectionPath: 'Authors',
Label: 'Authors',
SearchSupported: 'true',
Parameters: [
{ $Type: 'Common.ValueListParameterOut', LocalDataProperty: 'author_ID', ValueListProperty: 'ID'},
{ $Type: 'Common.ValueListParameterDisplayOnly', ValueListProperty: 'name'},
]
};
};
annotate CatalogService.Books with @(
UI.LineItem: [
{$Type: 'UI.DataField', Value: ID},
{$Type: 'UI.DataField', Value: title},
{$Type: 'UI.DataField', Value: stock},
],
UI.HeaderInfo: {
Title: { Value: title },
TypeName:'Book',
TypeNamePlural:'Books'
},
UI.Identification:
[
{$Type: 'UI.DataField', Value: ID},
{$Type: 'UI.DataField', Value: title},
{$Type: 'UI.DataField', Value: stock}
],
UI.Facets:
[
{
$Type:'UI.CollectionFacet',
Facets: [
{ $Type:'UI.ReferenceFacet', Label: 'General Info', Target: '@UI.Identification' }
],
Label:'Book Details',
},
{$Type:'UI.ReferenceFacet', Label: 'Orders', Target: 'orders/@UI.LineItem'},
]
);
Add some sample data in DB for test purpose using db/src/csv/Books.csv file. Test your html5 module from WebIDE testing tools.
Add Navigation Semantic object inside html module booklist
, file manifest.json
Add new Fiori Launchpad Site Module
in Project with name FioriCAPApplication1-fiori
Open CommonDataModel.json as Launchpad Editor and add your UI application using WebIDE wizard
Tempalte added additional XSUAA Resource in mta.yaml
file which need to replace with already created one FioriCAPApplication1-uaa
.
Update mta.yaml
file reference of portal_resources_FioriCAPApplication1
with name FioriCAPApplication1-portal
for naming standard
Build your application and deply to cf space.
Run the application Router
Create destination in Sub-Account Level with following information
sap-platform=CF
URL=<App_router_URL>
Name=PortalDemoApps
ProxyType=Internet
Type=HTTP
Authentication=NoAuthentication
Description=Portal Demo Application
In the side navigation panel of your subaccount, click Subscriptions
and enter Portal
in the search bar. Then click the Portal tile and Go to Application. The Site Manager
opens with the Site Directory in focus.
Click the icon in the side panel of the Site Manager to open the Content Manager
.
In the Content Manager, click + New > App
In properties
tab enter following
- Title: Books
- System:
DestinationName
created in previous step . Note that it may take several minutes until the destination that you created in step 2 appears in the System list. - App UI Technology : SAPUI5 (this is the type of app that you are adding).
- SAPUI5 Component Name : booklist - this is the registered name of the SAPUI5 component. To get this name, ask your developer to open the component.js file in SAP Web IDE - it is defined in the component.js file without the .component suffix as shown here: Find component name
Your screen will look like this:
Click the Navigation
tab and enter the following information
Click the Visualization
tab, Enter following information
Click the Everyone role to open the Role editor edit add Application Books
Click + New in the Content Manager and click Group to open the Group editor.
-
In the Assignments panel on the right, search for your
Books
app and the click the + to assign your app to this group. -
save the configuration.
-
Open any Sites which include lauchpad. View the application title.
- Remove portal application
Books
from Everyone Role - Add new Role
DemoUser
and add Book application in this Role - Update Portal site with
DemoUser
Role - Add your user to
DemoUser
Role from SCP Cockpit in Subaccount -> Security -> Trust Configuration for particular IDP
- Add following information to UI application
booklist
inmanifest.json
file
"sap.platform.cf": {
"oAuthScopes": [
"$XSAPPNAME.demouser"
]
}
- Build the application and deploy to SCP
- Add
demouser
role from Application toDemoUser
Role Collection in Subaccount -> Security -> Role Collection
- Update xs-app.json file for
booklist
(ui module) to pass security information to Service layer from :
{
"source": "/srv_api/(.*)$",
"target": "$1",
"authenticationType": "none",
"destination": "srv_api",
"csrfProtection": false
},
to :
{
"source": "/srv_api/(.*)$",
"target": "$1",
"authenticationType": "xsuaa",
"destination": "srv_api",
"csrfProtection": false
},
-> Add cat-service-auth.cds security annotation inside srv module
annotate CatalogService with @(requires: 'demouser');
- Extension Class can created by WebIDE Template
- Right click on srv module and create
Entity Operation Hooks
- Choose Entity which need to be extended
- This will generate new Java Class BooksHooksHandler.java for Handling Extension for Book Entity
This type of Extension is for doing simple database operation like insert, Delete, Read , Update with Entity Key
-
Let's create OData Function to read Book data from BookId.
-
Update Catalog Service CDS model to include function defination in file cat-service.cds
function GetBookDetails(id:Integer) returns Books;
Open previously created Java Class and add following Code
@Function(serviceName = "CatalogService", Name = "GetBookDetails")
public OperationResponse getBooks(OperationRequest functionRequest, ExtensionHelper extensionHelper) {
OperationResponse opResponse;
try {
// Retrieve the parameters of the function from the
// OperationRequest object
Map<String, Object> parameters = functionRequest.getParameters();
//Get the DataSourceHandler object from the ExtensionHelper. This is required
//to execute operations on the local HANA database
DataSourceHandler handler = extensionHelper.getHandler();
//Retrieve the Order ID from the request
Map<String, Object> booksKey = new HashMap<String, Object>();
booksKey.put("ID", String.valueOf(parameters.get("id")));
List<String> bookElements = Arrays.asList("ID","title","stock");
//Read the Order information from the local HANA database
EntityData bookData = handler.executeRead("Books", booksKey, bookElements);
List<EntityData> bookList = new ArrayList<EntityData>();
bookList.add(bookData);
// Return an instance of OperationResponse containing the list of book data
opResponse = OperationResponse.setSuccess().setEntityData(bookList).response();
} catch (Exception e) {
log.error("Error in GetBook: " + e.getMessage());
// Return an instance of OperationResponse containing the error in
// case of failure
ErrorResponse errorResponse = ErrorResponse.getBuilder()
.setMessage(e.getMessage())
.setCause(e)
.response();
opResponse = OperationResponse.setError(errorResponse);
}
return opResponse;
}
run srv with following URL
/odata/v2/CatalogService/GetBookDetails?id=123
Detail operation support from DataSourceHandler is provided in SCP Cloud SDK API help
More details operation details are provide in CAP documentation - Coming soon
This type shloud be used for complex query opertions.
To execute any OData operation on a CDS data source, first create a CDSDataSourceHandler
object passing a Connection
object (that contains the CDS data source connection) and the namespace of the requested entity.
The following code sample shows how you can create a CDSDataSourceHandler
object:
CDSDataSourceHandler dsHandler = DataSourceHandlerFactory.getInstance().getCDSHandler(getConnection(), req.getEntityMetadata().getNamespace());
The following code sample shows how you can create a Connection object that contains the CDS data source connection:
private static Connection getConnection() {
Connection conn = null;
InitialContext ctx;
try {
ctx = new InitialContext();
conn = ((DataSource) ctx.lookup("java:comp/env/jdbc/java-hdi-container")).getConnection();
} catch (Exception e) {
e.printStackTrace();
}
return conn;
}
- Query Operation
To implement the Query operation on a CDS data source, use the
CDSSelectQueryBuilder
and CDSQuery classes. TheCDSSelectQueryBuilder
class provides methods with which you can build the query and return it in aCDSQuery
object. Use theCDSDataSourceHandler
object to actually execute the query. The following is sample code that shows how you can implement it:
CDSQuery cdsQuery = new CDSSelectQueryBuilder("CatalogService.Books")
.top(2)
.skip(1)
.selectColumns("ID","title","stock")
.build();
CDSSelectQueryResult cdsSelectQueryResult = dsHandler.executeQuery(cdsQuery);
Uncomment the Operation Handler method which need to be extended
Complete Code for After Read Book Entity is as follow.
@AfterRead(entity = "Books", serviceName = "CatalogService")
public ReadResponse afterReadBooks(ReadRequest req, ReadResponseAccessor res, ExtensionHelper helper) {
EntityData data = res.getEntityData();
//TODO: add your custom logic / validations here...
try{
DataSourceHandler handler = helper.getHandler();
CDSDataSourceHandler dsHandler = DataSourceHandlerFactory.getInstance().getCDSHandler(getConnection(), req.getEntityMetadata().getNamespace());
CDSQuery cdsQuery = new CDSSelectQueryBuilder("CatalogService.Books")
.top(2)
.skip(1)
.selectColumns("ID","title","stock")
.build();
CDSSelectQueryResult cdsSelectQueryResult = dsHandler.executeQuery(cdsQuery);
data = cdsSelectQueryResult.getResult().get(0);
}
catch (Exception e) {
log.error("Error in GetBook: " + e.getMessage());
// Return an instance of OperationResponse containing the error in
// case of failure
ErrorResponse errorResponse = ErrorResponse.getBuilder()
.setMessage(e.getMessage())
.setCause(e)
.response();
return ReadResponse.setError(errorResponse);
}
// return res.getOriginalResponse(); //use this API if no change is required and the original response can be returned as output.
return ReadResponse.setSuccess().setData(data).response(); //use this API if the payload is modified.
//return ReadResponse.setError(ErrorResponse.getBuilder().setMessage("Read Operation Failed").response()); //use this API if should return error response.
}
Detail document can be found in CDS
For more complex Database operation JPA based opreation is also supported. Follow Tutorial for same