Skip to content
Search

    Working with CDS Models

    The Model Reflection API is a set of interfaces, which provide the ability to introspect a CDS model and retrieve details on the services, types, entities, and their elements that are defined by the model.

    Content

    The CDS Model

    The interface CdsModel represents the complete CDS model of the CAP application and is the starting point for the introspection.

    The CdsModel can be obtained from the EventContext:

    import com.sap.cds.services.EventContext;
    import com.sap.cds.reflect.CdsModel;
    
    @On(event = "READ", entity = "my.catalogservice.books")
    public void readBooksVerify(EventContext context) {
        CdsModel model = context.getModel();
       [...]
    }
    

    or, in Spring, be injected:

    @Autowired
    CdsModel model;
    

    On a lower level, the CdsModel can be obtained from the CdsDataStoreConnector, or using the read method from a CSN String or InputStream:

    InputStream csnJson = ...
    CdsModel model = CdsModel.read(csnJson);
    

    Examples

    The following examples are using this CDS model:

    namespace my.bookshop;
    
    entity Books {
      title  : localized String(111);
      author : Association to Authors;
      ...
    }
    
    entity Authors {
      key ID : Integer;
      ...
    }
    
    entity Orders {
      OrderNo  : String @title:'Order Number';
      ...
    }
    

    Get and Inspect an Element of an Entity

    In this example, we introspect the details of the type of the element title of the entity Books:

    CdsEntity books = model.getEntity("my.bookshop.Books");
    CdsElement title = books.getElement("title");
    
    boolean key = title.isKey();      // false
    CdsType type = title.getType();   // CdsSimpleType
    
    if (type.isSimple()) {   // true
      CdsSimpleType simple = type.as(CdsSimpleType.class);
    
      String typeName = simple.getQualifiedName();  // "cds.String"
      CdsBaseType baseType = simple.getType();      // CdsBaseType.STRING
      Class<?> javaType = simple.getJavaType();     // String.class
      Boolean localized = simple.get("localized");  // true
      Integer length = simple.get("length");        // 111
    }
    

    Get and Inspect All Elements of an Entity

    CdsEntity books = model.getEntity("my.bookshop.Books");	
    Stream<CdsElement> elements = books.elements();	
    

    The method elements() returns a stream of all elements of the given entity, structured type, or event. It’s important to note that the Model Reflection API doesn’t guarantee the element order to be exactly like in the source CSN document. However, the order is guaranteed to be stable during multiple consecutive model reads.

    In case the element names are known beforehand it’s recommended to access them by name through the getElement(String name) method.

    Get and Inspect an Association Element of an Entity

    We can also analyze the details of an association:

    CdsElement authorElement = book.getAssociation("author");
    CdsAssociationType toAuthor = authorElement.getType();
    
    CdsEntity author = toAuthor.getTarget(); // Entity: my.bookshop.Authors
    boolean association = toAuthor.isAssociation();   // true
    boolean composition = toAuthor.isComposition();   // false
    
    Cardinality cardinality = toAuthor.getCardinality();
    String sourceMax = cardinality.getSourceMax();    // "*"
    String targetMin = cardinality.getTargetMin();    // "0"
    String targetMax = cardinality.getTargetMax();    // "1"
    
    Stream<CdsElement> keys = toAuthor.keys();  // Stream: [ ID ]
    Optional<CqnExpression> onCondition = toAuthor.onCondition(); // empty
    

    Find an Annotation by Name and Get Its Value

    Here, we programmatically check if the element OrderNo carries the annotation title and set the value of displayName depending on the presence of the annotation:

    CdsEntity order = model.getEntity("my.bookshop.Orders");
    CdsElement orderNo = order.getElement("OrderNo");
    
    Optional<CdsAnnotation<String>> annotation = orderNo
            .findAnnotation("title");
    String displayName = annotation.map(CdsAnnotation::getValue)
            .orElse(orderNo.getName());   // "Order Number"
    

    Filter a Stream of Services for non-abstract Services

    Using a stream we determine all non-abstract services:

    Stream<CdsService> services = model.services()
        .filter(s -> !s.isAbstract());
    List<CdsService> serviceList = services.collect(Collectors.toList());
    

    Filter a Stream of Entities by Namespace

    The static method com.sap.cds.reflect.CdsDefinition.byNamespace allows to create a predicate to filter a stream of definitions (for example, entities, elements, …) for definitions contained in a given namespace:

    import static com.sap.cds.reflect.CdsDefinition.byNamespace;
    ...
    
    Stream<CdsEntity> entities = model.entities()
        .filter(byNamespace("my.bookshop"));
    

    Get All Elements with Given Annotation

    The static method com.sap.cds.reflect.CdsAnnotatable.byAnnotation allows to create a predicate to filter a stream of annotatable model components (for example, entities, elements, …) for components that carry a given annotation:

    import static com.sap.cds.reflect.CdsAnnotatable.byAnnotation;
    ...
    
    CdsEntity order = model.getEntity("my.bookshop.Orders");
    Stream<CdsElement> elements = order.elements()
        .filter(byAnnotation("title"));
    

    Feature Toggles

    Feature Toggles and Active Feature Set

    Feature toggles allow to dynamically enable or disable parts of an application at runtime or to alter the behaviour depending on features.

    Feature toggles can be used for different purposes. They can be used as release toggles to selectively enable some features for some customers only based on a deployment vector. Or they can be used as runtime toggles to dynamically enable or disable selected features for selected users.

    CAP Java does not make any assumption how the set of enabled features (active feature set) is determined. This could be based on user ID, user role, user tenant, or any other information such as an HTTP header or an external feature toggle service.

    Features in CDS Models

    Features are modeled in CDS by dividing up CDS code concerning separate features into separate subfolders of a common fts folder of your project, as shown by the following example:

    |-- [db]
    |   |-- my-model.cds
    |   `-- ...
    |-- [srv]
    |   |-- my-service.cds
    |   `-- ...
    `-- [fts]
        |-- [X]
        |   |-- model.cds
        |   `-- ...
        |-- [Y]
        |   |-- feature-model.cds
        |   `-- ...
        `-- [Z]
            |-- wrdlbrmpft.cds
            `-- ...
    

    In this example, three CDS features X, Y and Z are defined. Note, that the name of a feature (by which it is referenced in a feature toggle) corresponds to the name of the feature’s subfolder. A CDS feature can contain arbitrary CDS code. It can either define new entities or extensions of existing entities.

    The database schema resulting from CDS build at design time contains all features. This is required to serve the base model and all combinations of features at runtime.

    The Model Provider Service

    feature-toggles.drawio

    At runtime, per request, an effective CDS model is used that reflects the active feature set. To obtain the effective model that the runtime delegates to the Model Provider Service, which uses this feature set to resolve the CDS model code located in the fts folder of the active features and compiles to effective CSN and EDMX models for the current request to operate on.

    The active features set can’t be changed within an active transaction.

    Toggling SAP Fiori UI Elements

    In an SAP Fiori elements application, the UI is captured with annotations in the CDS model. Hence, toggling of SAP Fiori elements annotations is already leveraged by the above concept: To enable toggling of such annotations (and thus UI elements), it’s required that the EDMX returned by the $metadata respects the feature vector. This is automatically achieved by maintaining different model variants according to activated features as described in the previous section.

    Features on the Database

    As CDS features are reflected in database artifacts, the database needs to be upgraded when new features are introduced in the CDS model. If a feature is enabled, the corresponding database artifacts are already present and no further database change is required.

    Only when a particular feature is turned on, the application is allowed to access the corresponding part of the database schema. The CAP framework ensures this by exposing only the CDS model that corresponds to a certain feature vector. The CAP framework accesses database entities based on the currently active CDS model only. This applies in particular to SELECT * requests for which the CAP framework returns all columns defined in the current view on the model, and not all columns persisted on the database.

    Feature Toggles Info Provider

    In CAP Java, the active feature set in a particular request is represented by the FeatureTogglesInfo. On each request, the runtime uses the FeatureTogglesInfoProvider to create the request-dependent FeatureTogglesInfo object, which is exposed in the current RequestContext by getFeatureTogglesInfo().

    By default all features are deactivated (FeatureTogglesInfo represents an empty set).

    From Mock User Configuration

    If mock users are used, a default FeatureToggleProvider is registered, which assigns feature toggles to users based on the mock user configuration. Feature toggles can be configured per user or per tenant. The following configuration enables the feature wobble for the user Bob while for Alice the features cruise and parking are enabled:

    cds:
      security:
        mock:
          users:
            - name: Bob
              tenant: CrazyCars
              features:
                - wobble
            - name: Alice
              tenant: SmartCars
              features:
                - cruise
                - parking
    

    Custom Implementation

    Applications can implement a custom FeatureTogglesInfoProvider that computes a FeatureTogglesInfo based on the request’s UserInfo and ParameterInfo.

    The following example demonstrates a feature toggles info provider that enables the feature isbn if the user has the expert role:

    @Component
    public class DemoFTProvider implements FeatureTogglesInfoProvider {
        @Override
        public FeatureTogglesInfo get(UserInfo userInfo, ParameterInfo paramInfo) {
            Map<String, Boolean> featureToggles = new HashMap<>();
            if (userInfo.hasRole("expert")) {
                featureToggles.put("isbn", true);
            }
            return FeatureTogglesInfo.create(featureToggles);
        }
    }
    

    This feature toggles provider is automatically registered and used as Spring bean by means of the annotation @Component. At each request, the CAP Java runtime calls the method get(), which determines the active features based on the logged in user’s roles.

    Defining Feature Toggles for Internal Service Calls

    It is not possible to redefine the feature set within an active request context as this would result in a model change. However, if there is no active request context such as in a new thread, you can specify the feature set while Defining Request Contexts.

    In the following example, a Callable is executed in a new thread resulting in an initial request context. In the definition of the request context the feature toggles are defined that will be used for the statement execution:

    @Autowired
    CdsRuntime runtime;
    
    @Autowired
    PersistenceService db;
    
    FeatureTogglesInfo isbn = FeatureTogglesInfo.create(Collections.singletonMap("isbn", true));
    
    ...
    
    Future<Result> result = Executors.newSingleThreadExecutor().submit(() -> {
      return runtime.requestContext().featureToggles(isbn).run(rc -> {
        return db.run(Select.from(Books_.CDS_NAME));
      });
    });
    

    Deploy with Confidence

    Deploy with Confidence (DwC) provides a util-cap library, which brings an implementation of FeatureTogglesInfoProvider that determines the feature set based on DwC headers.

    Using Feature Toggles in Custom Code

    Custom code, which depend on a feature toggle can evaluate the FeatureTogglesInfo to determine if the feature is enabled. The FeatureTogglesInfo can be obtained from the RequestContext or EventContext by the getFeatureTogglesInfo() method or by dependency injection. This is shown in the following example where custom code depends on the feature discount:

    @After
    protected void subtractDiscount(CdsReadEventContext context) {
        if (context.getFeatureTogglesInfo().isEnabled("discount")) {
            // Custom coding executed when feature "discount" is active
            // ...
        }
    }