Hi Hanif, This will be possible with OData 4.0, in OData 2.0 complex properties are single-valued.
A typical work-around with OData 2.0 is to use a separate entity type and a navigation property with cardinality * instead of the list-valued complex property. In many use cases the items in the list actually can be uniquely identified, and are in fact stored in a separate table. So this "work-around" actually is making the underlying data model visible again. What is your use case? Regards! --Ralf -----Original Message----- From: Rajabali, Hanif [mailto:[email protected]] Sent: Tuesday, 11. February 2014 17:39 To: Klevenz, Stephan Cc: Xu, Yao Feng; [email protected] Subject: How to resolve 1 Entity type that has a property which is a list of a complex type (cadinality 0.. *) Hi Stephan and the great Olingo community! I have a situation where I'm trying to model an oData service from a webservice that returns back a lot of data in the form of nested complex types (a very complex xsd) . I basically have 1 entity type that represents the main webservice response object. And that entity type has several complex/simple type properties that I've mapped to match. When ready to write the odata response, I can map them to a List<Map<String,Object>> as per what odata expects by using reflection etc etc..... the problem I'm having is if I have a List of complex type as part of my entity type. Is there a way for me to create a complex property and define it as a list or collection that has cardinality 0..*? I don't have any navigation properties as I only have the 1 entity. So once I get it correctly defined, my metadata should show that particular field/property as a collection am I correct? Any help with some code examples/snippets would be greatly appreciated! Thank you! Hanif On 16.01.14 22:44, "Rajabali, Hanif" <[email protected]> wrote: >Hello Sven and the olingo community! Needed your help again please :) > As it stands, we are using the olingo library to generate odata >services. We are sort of using the odata services as a proxy to invoke >other services so its not really being used to perform CRUD operation on >a backend database per se, but more so to simply relay the request(s) to >underlying service(s). For example, we have an Account.svc which >simply gets accounts from another disparate rpc service. We have >Product.svc which gets products from another webservice. I realize >this is not really what odata is meant to be used for but using odata to >communicate with a ui layer (sap ui5) is a strict requirement for us. >So now, since we have multiple services, we have not defined a >singleprocessor as in the car/manufacturer example. Instead, we have >defined separate processors which extend from OdataSingleProcessor i.e) >AccountProcessor, ProductProcessor and have defined a delegator (also >extends OdataSingleprocessor) which through reflection, and our own >defined annotations, determines, which processor to use. We have also >defined our own annotations to represent, simple/complex types which get >mapped at runtime through the edmprovider instead of hard coding entity >field simpl/complex types as shown in the sample app. (Unless there is >something out of the box we can use??) > >We now need to implement FunctionImport. The car/manufacturer example >defines it in the edmprovider but doesn't provide a concrete >implementation for us to use as an example. We would like to create >such functionimports to be able to in turn call services that don't >really tie into an EntityType. We also don't use JPA... can you >illustrate how we might perform something like: >http://myhost/MyService.svc/MyFunctionImport(parameter1....parameterN)? > >Thank you so much! >Hanif > >-----Original Message----- >From: Kobler-Morris, Sven [mailto:[email protected]] >Sent: Thursday, December 19, 2013 5:00 AM >To: [email protected] >Subject: FW: How to implement a filter or query using Olingo API > >Hello Hanif, > >using the filter is the better option, because that is what the $filter >system query parameter is made for (similar to the "where " clause >in SQL statements). A ODATA client can request only entities which >pass the filter expression. > >To avoid a manual parsing of the $filter parameter the odata library >does the parsing and creates a filter tree which can be obtained from the >UriInfo object supplied as input parameter "uriInfo" on the "readEntity" >method. The filter tree can be used directly or with help of the Visitor >pattern. > >Generally you can use the filter tree to create a query string which is >understood by your data layer and pass this query string to the data >layer (potentially insecure) or you can filter a entity set manually in >your layer. > >I recommend to implement the interface "ExpressionVisitor" to either >create the query string for the data layer or to the manual filtering. > >An potential solution for manual filtering would be to implement >and ExpressionVisitor and use this visitor on each entity set/data >record >you want to filter. > >E.g. >... >public class AccountFilter implements ExpressionVisitor >... >use the methods: >visitMember -> to read and validate the propertyName > (from your sample only accountName is >allowed) > and return the column name >visitLiteral -> to read the accountName value (e.g. "Account1") > and return it >visitBinary -> if the parameter is "eq" return > whether property accountName on the current > entityset/datarecord is equal to the literal or not >... >As sample a rough flow would be: >- read the entityset ( all records) >- get the filter tree from uriInfo >- for each record >- if ( filtertree->accept(yourAccountFilter) != true) { > delete record from entityset > } >- next record >- serialize the remaining records > >The call sequence for $filter=AccountName eq 'Account1') would be >1. visitMember >2. visitLiteral >3. visitBinary (left operant is "AccountName" right opterand is >"Account1") > >Consider also how your service should behave in case of more complex >filters >Like: >$filter='Account1' eq AccountName >$filter='Account1' eq AccountName or AccountName eq 'Acount2' > >However this is not the fastest solution. It is always faster to >transform the >filtertree into a query which can be used directly in your datalayer, but >always >consider security checks before using this to avoid script injection >etc... > >regards, >Sven > >---- > >On 17.12.13 22:55, "Hanif Rajabali" <[email protected]> wrote: > >>Hello all, sorry if this is too rudimentary of a question but I have >>setup an oData service with the help of the great tutorials posted on the >>olingo site. The tutorials cover the Resource path but I'd like to now >>perform a system or custom query. Below I've included my basic >>$metadata document which has 2 simple entities: Account/Producer >>(EntitySets: Accounts/Producers). As it stands I can perform an >>operation like /Accounts(1) which is taken care of in my implementation >>of readEntity() in my ODataSingleProcesser shown below. Now I'd like >>to get say an account by 'AccountName'. So would it be system $filter >>like: /Accounts?$filter=AccountName eq 'Account1' or would it be a >>custom query like: /Accounts?AccountName='Account1' >> >> >> >>And, where would I implement it? Within readEntity? If so, could you >>give me some guidance on the implementation? I saw the sample of the >>visitor pattern to transform an odata query into a jdbc query but my >>odata service is connecting to another disparate service: >>this.accountService so I just need to basically parse the query and then >>perform the corresponding service call based on the query name/value >>pair. Should I leverage what was shown in the visitor example? If so, >>how does that 'plugin' to my OdataSingleProcessor implementation? >> >> >> >>public class WPODataSingleProcessor extends ODataSingleProcessor { >> >>..... >> >>public ODataResponse readEntity(GetEntityUriInfo uriInfo, String >>contentType) throws ODataException { >> >> logger.info("custom query options: " + >>uriInfo.getCustomQueryOptions()); >> >> if (uriInfo.getNavigationSegments().size() >>== 0) { >> >> EdmEntitySet entitySet = >>uriInfo.getStartEntitySet(); >> >> WPEntitySet startEntitySet = >>WPEntitySet.fromString(entitySet.getName()); >> >> >> >> int id; >> >> switch(startEntitySet) { >> >> case ACCOUNTS: >> >> id = >>getKeyValue(uriInfo.getKeyPredicates().get(0)); >> >> Map<String, >>Object> account = this.accountService.getAccount(id); >> >> >> >> if (account >>!= null) { >> >> >> URI serviceRoot = getContext().getPathInfo().getServiceRoot(); >> >> >> ODataEntityProviderPropertiesBuilder propertiesBuilder = >>EntityProviderWriteProperties.serviceRoot(serviceRoot); >> >> >> >> >> return EntityProvider.writeEntry(contentType, entitySet, account, >>propertiesBuilder.build()); >> >> } >> >> >> >> case PRODUCERS: >> >> id = >>getKeyValue(uriInfo.getKeyPredicates().get(0)); >> >> Map<String, >>Object> producer = this.producerService.getProducer(id); >> >> >> >> if (producer >>!= null) { >> >> >> URI serviceRoot = getContext().getPathInfo().getServiceRoot(); >> >> >> ODataEntityProviderPropertiesBuilder propertiesBuilder = >>EntityProviderWriteProperties.serviceRoot(serviceRoot); >> >> >> >> >> return EntityProvider.writeEntry(contentType, entitySet, producer, >>propertiesBuilder.build()); >> >> } >> >> >> } >> >> >> >> throw new >>ODataNotFoundException(ODataNotFoundException.ENTITY); >> >> >> >> } >> >> >> >> throw new ODataNotImplementedException(); >> >> } >> >> >> >>... >> >> >> >>} >> >> >> >> >> >>------------------- >> >>My $metadata: >> >>------------------- >> >><edmx:Edmx xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx" >>Version="1.0"> >> >><edmx:DataServices >>xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" >>m:DataServiceVersion="1.0"> >> >><Schema xmlns="http://schemas.microsoft.com/ado/2008/09/edm" >>Namespace="com.sap.workplace.uiservice.odata"> >> >><EntityType Name="Account"> >> >><Key> >> >><PropertyRef Name="Id"/> >> >></Key> >> >><Property Name="Id" Type="Edm.Int32" Nullable="false"/> >> >><Property Name="AccountId" Type="Edm.String" Nullable="false"/> >> >><Property Name="AccountType" Type="Edm.String" Nullable="false"/> >> >><Property Name="AccountName" Type="Edm.String" Nullable="false"/> >> >><Property Name="AccountNumber" Type="Edm.String" Nullable="false"/> >> >><Property Name="Address" >>Type="com.sap.workplace.uiservice.odata.Address"/> >> >></EntityType> >> >><EntityType Name="Producer"> >> >><Key> >> >><PropertyRef Name="Id"/> >> >></Key> >> >><Property Name="Id" Type="Edm.Int32" Nullable="false"/> >> >><Property Name="ProducerName" Type="Edm.String" Nullable="false"/> >> >><Property Name="ProducerId" Type="Edm.String" Nullable="false"/> >> >><Property Name="FirstName" Type="Edm.String" Nullable="false" >>MaxLength="100"/> >> >><Property Name="LastName" Type="Edm.String" Nullable="false" >>MaxLength="100"/> >> >><Property Name="Address" >>Type="com.sap.workplace.uiservice.odata.Address"/> >> >></EntityType> >> >><ComplexType Name="Address"> >> >><Property Name="Address1" Type="Edm.String" Nullable="true"/> >> >><Property Name="Address2" Type="Edm.String" Nullable="true"/> >> >><Property Name="City" Type="Edm.String" Nullable="true"/> >> >><Property Name="State" Type="Edm.String" Nullable="true"/> >> >><Property Name="Country" Type="Edm.String" Nullable="true"/> >> >><Property Name="Zip" Type="Edm.String" Nullable="true"/> >> >></ComplexType> >> >><EntityContainer Name="SAPWorkplaceEntityContainer" >>m:IsDefaultEntityContainer="true"> >> >><EntitySet Name="Accounts" >>EntityType="com.sap.workplace.uiservice.odata.Account"/> >> >><EntitySet Name="Producers" >>EntityType="com.sap.workplace.uiservice.odata.Producer"/> >> >></EntityContainer> >> >></Schema> >> >></edmx:DataServices> >> >></edmx:Edmx> >> >> >> >> >> >
