├── .dockerignore ├── .gitignore ├── Dockerfile ├── LICENSE.txt ├── README.md ├── etc ├── docker.sh ├── entity-data-model-structure.mdj └── entity-data-model-structure.png ├── nb-configuration.xml ├── pom.xml └── src └── main ├── java └── com │ └── bloggingit │ └── odata │ ├── edm │ ├── annotation │ │ ├── EdmComplexType.java │ │ ├── EdmEntitySet.java │ │ ├── EdmEntityType.java │ │ ├── EdmFacets.java │ │ ├── EdmKey.java │ │ └── EdmProperty.java │ └── enumeration │ │ └── EdmValueType.java │ ├── exception │ └── EntityDataException.java │ ├── model │ ├── Author.java │ ├── BaseEntity.java │ ├── Book.java │ ├── ContactInfo.java │ └── Gender.java │ ├── olingo │ ├── edm │ │ └── meta │ │ │ ├── EntityMetaData.java │ │ │ ├── EntityMetaDataBuilder.java │ │ │ ├── EntityMetaDataContainer.java │ │ │ ├── EntityMetaProperty.java │ │ │ ├── EntityMetaPropertyComplex.java │ │ │ ├── EntityMetaPropertyEntity.java │ │ │ ├── EntityMetaPropertyEnum.java │ │ │ └── EntityMetaPropertyPrimitve.java │ └── v4 │ │ ├── factory │ │ └── OlingoObjectFactory.java │ │ ├── mapper │ │ ├── FQNMapper.java │ │ ├── OlingoObjectMapper.java │ │ └── OlingoTypeMapper.java │ │ ├── processor │ │ ├── AbstractEntityMetaDataProcessor.java │ │ ├── DataCollectionProcessor.java │ │ ├── DataEntityProcessor.java │ │ ├── DataPrimitiveProcessor.java │ │ └── DataPrimitiveValueProcessor.java │ │ ├── provider │ │ └── AnnotationEdmProvider.java │ │ ├── service │ │ └── OlingoDataService.java │ │ ├── servlet │ │ └── ODataDemoServlet.java │ │ └── util │ │ ├── DefaultValue.java │ │ ├── ReflectionUtils.java │ │ └── UriInfoUtil.java │ └── storage │ └── InMemoryDataStorage.java └── webapp ├── META-INF └── context.xml ├── WEB-INF ├── beans.xml └── web.xml └── index.html /.dockerignore: -------------------------------------------------------------------------------- 1 | # See https://docs.docker.com/engine/reference/builder/#dockerignore-file for more about ignoring files. 2 | 3 | 4 | # prevent local modules and log files from being copied into the docker image 5 | node_modules 6 | *.log 7 | 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /*/target 7 | /*/build-tools 8 | 9 | # dependencies 10 | /node_modules 11 | yarn.lock 12 | 13 | # IDEs and editors 14 | /.idea 15 | .project 16 | .classpath 17 | .c9/ 18 | *.launch 19 | .settings/ 20 | *.sublime-workspace 21 | 22 | # IDE - VSCode 23 | .vscode/* 24 | !.vscode/settings.json 25 | !.vscode/tasks.json 26 | !.vscode/launch.json 27 | !.vscode/extensions.json 28 | 29 | # misc 30 | /.sass-cache 31 | /connect.lock 32 | /coverage/* 33 | /libpeerconnection.log 34 | npm-debug.log 35 | testem.log 36 | /typings 37 | 38 | # e2e 39 | /e2e/*.js 40 | /e2e/*.map 41 | 42 | #System Files 43 | .DS_Store 44 | Thumbs.db 45 | /target/ -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # 2 | # Build an Apache TomEE container and deploy the WAR-Archives 3 | # 4 | # ##################### INFOS ###################### 5 | # - Config path: /usr/local/tomee/conf 6 | # - Webapp path: /usr/local/tomee/webapps 7 | # - User for the Tomcat Manager Application: tomee:tomee 8 | # 9 | # - ROOT App: /admin e.g. http://localhost:8080/admin 10 | # - Quickstart App: /angular2-qickstart-javaee7-application e.g. http://localhost:8080/angular2-qickstart-javaee7-application 11 | # - Quickstart Angular: /angular2-qickstart-javaee7-client-web e.g. http://localhost:8080/angular2-qickstart-javaee7-client-web 12 | # ##################### INFOS ###################### 13 | # 14 | # 15 | # ##################### DOCKER ##################### 16 | # BUILD 17 | # docker build -t angular2/javaee7-quickstart:1.0.0 . 18 | # 19 | # RUN (add -d parameter to start a container in detached mode) 20 | # docker run -P -it --rm -p 8080:8080 --name angular2-javaee7-quickstart angular2/javaee7-quickstart:1.0.0 21 | # 22 | # STOP 23 | # docker stop angular2-javaee7-quickstart 24 | # 25 | # REMOVE CONTAINER AND IMAGE 26 | # docker rm -f angular2-javaee7-quickstart ; docker rmi -f angular2/javaee7-quickstart:1.0.0 27 | # 28 | # LOGIN INTO CONTAINER 29 | # docker exec -i -t angular2-javaee7-quickstart /bin/bash 30 | # 31 | # COPY FILE FROM CONTAINER TO HOST 32 | # docker cp angular2-javaee7-quickstart:/usr/local/tomee/conf/web.xml ./web.xml 33 | # 34 | # ##################### DOCKER ##################### 35 | # 36 | # 37 | 38 | # build file based on TomEE 7.0.1 Plume 39 | FROM tomee:8-jdk-7.0.1-plume 40 | MAINTAINER "Markus Eschenbach " 41 | 42 | 43 | # ************* MODIFY CONTAINER FILES ************* 44 | 45 | # rename ROOT application folder to admin 46 | RUN mv /usr/local/tomee/webapps/ROOT /usr/local/tomee/webapps/admin 47 | 48 | # create new ROOT application folder and index.jsp welcome file 49 | RUN mkdir /usr/local/tomee/webapps/ROOT && echo "It works!" >> /usr/local/tomee/webapps/ROOT/index.jsp 50 | 51 | # add admin user tomee:tomee 52 | RUN sed -i '/<\/tomcat-users>/d' /usr/local/tomee/conf/tomcat-users.xml 53 | RUN echo " \ 54 | \ 55 | \ 56 | \ 57 | " >> /usr/local/tomee/conf/tomcat-users.xml 58 | 59 | 60 | # **************** INSTALL TOOLS ***************** 61 | 62 | # Avoid warnings during installsets 63 | # variable only live during the build process 64 | ARG DEBIAN_FRONTEND=noninteractive 65 | 66 | # Installing the 'apt-utils' package gets rid of the 'debconf: delaying package configuration, since apt-utils is not installed' 67 | # error message when installing any other package with the apt-get package manager. 68 | RUN apt-get update \ 69 | && apt-get install -y --no-install-recommends \ 70 | apt-utils \ 71 | && rm -rf /var/lib/apt/lists/* 72 | 73 | # make sure the package repository is up to date and install tools like nano etc. 74 | RUN apt-get update \ 75 | && apt-get install -y --no-install-recommends \ 76 | nano 77 | 78 | 79 | # *************** ADD APPLICATIONS *************** 80 | 81 | ADD module-application/target/angular2-qickstart-javaee7-application.war /usr/local/tomee/webapps/angular2-qickstart-javaee7-application.war 82 | ADD module-client-web/target/angular2-qickstart-javaee7-client-web.war /usr/local/tomee/webapps/angular2-qickstart-javaee7-client-web.war 83 | 84 | 85 | # ******************** EXPOSE ******************** 86 | 87 | EXPOSE 8080 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | #=================================================================== 2 | # ::::::::::::::: www.blogging-it.com ::::::::::::::: 3 | # 4 | # Copyright (C) 2017 Markus Eschenbach. All rights reserved. 5 | # 6 | # 7 | # This software is provided on an "as-is" basis, without any express or implied warranty. 8 | # In no event shall the author be held liable for any damages arising from the 9 | # use of this software. 10 | # 11 | # Permission is granted to anyone to use this software for any purpose, 12 | # including commercial applications, and to alter and redistribute it, 13 | # provided that the following conditions are met: 14 | # 15 | # 1. All redistributions of source code files must retain all copyright 16 | # notices that are currently in place, and this list of conditions without 17 | # modification. 18 | # 19 | # 2. All redistributions in binary form must retain all occurrences of the 20 | # above copyright notice and web site addresses that are currently in 21 | # place (for example, in the About boxes). 22 | # 23 | # 3. The origin of this software must not be misrepresented; you must not 24 | # claim that you wrote the original software. If you use this software to 25 | # distribute a product, an acknowledgment in the product documentation 26 | # would be appreciated but is not required. 27 | # 28 | # 4. Modified versions in source or binary form must be plainly marked as 29 | # such, and must not be misrepresented as being the original software. 30 | # 31 | # ::::::::::::::: www.blogging-it.com ::::::::::::::: 32 | #=================================================================== -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ODataV4 - JavaEE - Example - Apache Olingo 2 | =========================================== 3 | 4 | This example application is inspired by the `Basic Tutorial: Create an OData V4 Service with Olingo` which can be found in the [OData 4.0 Java Library Documentation](https://olingo.apache.org/doc/odata4/index.html). 5 | The implementation of the OData service is based on the [Apache Olingo OData 4.0 Library for Java](https://olingo.apache.org/doc/odata4/download.html). 6 | 7 | Afterwards the Web Application is deployed on a Java EE server, the OData service can be invoked from a browser or a http client and will provide the data according to the [OData V4 specification](http://www.odata.org/documentation). The service will display a list of books and a few properties that describe each book. 8 | 9 | Visit my blog for more background informations about this project. 10 | 11 | [OData V4 – Implementierung eines Services unter Java EE mittels Apache Olingo](https://www.blogging-it.com/odata-v4-implementierung-eines-services-unter-java-ee-mittels-apache-olingo/programmierung/java/javaee.html) (german) 12 | 13 | 14 | # Infrastructure 15 | 16 | - Java 1.8 17 | - Java EE 18 | - Maven 19 | - Apache TomEE 20 | - HTTP Web-Servlet 21 | - Apache Olingo 4.3.0 (2016-09-19) 22 | 23 | 24 | # Scenario 25 | 26 | The OData service in this example will implement the following simple sample model with the `Book` and `Author` entities. 27 | 28 | ![entity-data-model-structure](etc/entity-data-model-structure.png "entity data model structure") 29 | 30 | 31 | # Custom Entity Annotations 32 | 33 | There is a `Annotation Processor` extension available for the Apache Olingo OData **2.0** library implementation. Unfortunately, there is currently no similar extension for the **4.0** library implementation. That is why I decided to create an own annotation processor, which provided support for elementary requirements for this example. 34 | 35 | 36 | # Persistence Data Provider 37 | 38 | At the moment there is no database connection implemented to provide data for the OData service. To keep it simple, the class `InMemoryDataStorage` provides an in-memory data storage. 39 | 40 | # Implemented OData Service Requests 41 | 42 | - read Service Document 43 | - read Metadata Document 44 | - read all book or author data 45 | - read single book or author data 46 | - read single book or author property value 47 | - read single book or author property value (plain text) 48 | - create new book or author 49 | - update existing book or author data 50 | - delete existing book or author data 51 | - count number of all books or authors data 52 | 53 | 54 | 55 | ## Request: Read Service Document 56 | 57 | Furthermore, OData specifies the usage of the so-called Service Document. The user can see which entity collections are offered by the OData service. 58 | 59 | Request: 60 | 61 | ``` 62 | PATH: / 63 | METHOD: GET 64 | ``` 65 | 66 | **Example** 67 | 68 | ``` 69 | http://localhost:8080/odatav4-javaee-example-apache-olingo/api/servlet/v1/odatademo.svc 70 | ``` 71 | 72 | Result: 73 | 74 | The expected result is the Service Document which displays informations of the entity container: 75 | 76 | ```json 77 | { 78 | "@odata.context": "$metadata", 79 | "value": 80 | [ 81 | { 82 | "name": "AuthorSet", 83 | "url": "AuthorSet" 84 | }, 85 | { 86 | "name": "BookSet", 87 | "url": "BookSet" 88 | } 89 | ] 90 | } 91 | ``` 92 | 93 | ## Request: Read Metadata Document 94 | 95 | According to the OData specification, an OData service has to declare its structure in the so-called Metadata Document. This document defines the contract, such that the user of the service knows which requests can be executed, the structure of the result and how the service can be navigated. 96 | 97 | Request: 98 | 99 | ``` 100 | PATH: /$metadata 101 | METHOD: GET 102 | ``` 103 | 104 | **Example** 105 | 106 | ``` 107 | http://localhost:8080/odatav4-javaee-example-apache-olingo/api/servlet/v1/odatademo.svc/$metadata 108 | ``` 109 | 110 | Result: 111 | 112 | The expected result is the Metadata Document that displays the Schema, EntityType, EntityContainer and the EntitySet. 113 | 114 | ```xml 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | ``` 159 | 160 | ## Request: Read Book Or Author Entity Collection 161 | 162 | This request will display a list of books or authors and some properties that describe each entity. 163 | 164 | Add the optional `format` parameter to the request url, which contains information about the content type that is requested. This means that the user has the choice to receive the data either in XML or in JSON (default). 165 | If the content type is `application/json;odata.metadata=minimal`, then the payload is formatted in JSON. 166 | The content format can as well be specified via the request header `Accept: application/json;odata.metadata=minimal`. 167 | 168 | Internally the `DataCollectionProcessor#readEntityCollection` implementation of the OData service will be invoked. 169 | 170 | Request: 171 | 172 | ``` 173 | PATH: /BookSet or /AuthorSet 174 | METHOD: GET 175 | Header (optional): Accept: application/json;odata.metadata=minimal 176 | ``` 177 | 178 | **Example** 179 | 180 | ``` 181 | http://localhost:8080/odatav4-javaee-example-apache-olingo/api/servlet/v1/odatademo.svc/BookSet 182 | 183 | or 184 | 185 | http://localhost:8080/odatav4-javaee-example-apache-olingo/api/servlet/v1/odatademo.svc/BookSet?$format=xml 186 | 187 | or 188 | 189 | http://localhost:8080/odatav4-javaee-example-apache-olingo/api/servlet/v1/odatademo.svc/BookSet?$format=application/json;odata.metadata=minimal 190 | ``` 191 | 192 | Result: 193 | 194 | The expected result is the list of book entries: 195 | 196 | ```json 197 | { 198 | "@odata.context": "$metadata#BookSet", 199 | "value": 200 | [ 201 | { 202 | "title": "Book Title 1", 203 | "description": "This is the description of book 1", 204 | "releaseDate": "2011-07-21", 205 | "price": 9.95, 206 | "inStock": true, 207 | "id": 1 208 | }, 209 | { 210 | "title": "Book Title 2", 211 | "description": "This is the description of book 2", 212 | "releaseDate": "2015-08-06", 213 | "price": 5.99, 214 | "inStock": true, 215 | "id": 2 216 | }, 217 | { 218 | "title": "Book Title 3", 219 | "description": "This is the description of book 3", 220 | "releaseDate": "2013-05-12", 221 | "price": 14.5, 222 | "inStock": false, 223 | "id": 3 224 | } 225 | ] 226 | } 227 | ``` 228 | 229 | ### System Query Option $count 230 | 231 | The `$count` query option allows to request a count of the matching resources. The number will be included with the resources in the response. The $count system query option with a value of `true` specifies that the total count of items within a collection matching the request be returned along with the result. 232 | 233 | OData V4 Spec Hint: The $count system query option ignores any $top, $skip, or $expand query options, and returns the total count of results across all pages including only those results matching any specified $filter and $search. 234 | 235 | **Example** 236 | 237 | ``` 238 | http://localhost:8080/odatav4-javaee-example-apache-olingo/api/servlet/v1/odatademo.svc/BookSet?$count=true 239 | ``` 240 | 241 | 242 | ```json 243 | { 244 | "@odata.context": "$metadata#BookSet", 245 | "@odata.count": 3, 246 | "value": 247 | [ 248 | { 249 | ... 250 | ``` 251 | 252 | 253 | ## Request: Read Single Book Or Author Entity 254 | 255 | This request will display the details of a single book or author entity, which has the corresponding ID. 256 | 257 | Internally the `DataEntityProcessor#readEntity` implementation of the OData service will be invoked. 258 | 259 | Request: 260 | 261 | ``` 262 | PATH: /BookSet(ID_OF_THE_BOOK) or /AuthorSet(id=ID_OF_THE_BOOK) 263 | METHOD: GET 264 | Header (optional): Accept: application/json;odata.metadata=minimal 265 | ``` 266 | 267 | **Example** 268 | 269 | ``` 270 | http://localhost:8080/odatav4-javaee-example-apache-olingo/api/servlet/v1/odatademo.svc/BookSet(1) 271 | ``` 272 | 273 | Result: 274 | 275 | The expected result is a response with the details of a single book with the id 1. 276 | 277 | 278 | ```json 279 | { 280 | "@odata.context": "$metadata#BookSet/$entity", 281 | "title": "Book Title 1", 282 | "description": "This is the description of book 1", 283 | "releaseDate": "2011-07-21", 284 | "price": 9.95, 285 | "inStock": true, 286 | "id": 1 287 | } 288 | ``` 289 | 290 | ## Request: Read Single Book or Author Property 291 | 292 | If you don't want to receive the full payload of the entity, you can use this request to receive only the value of the property of the OData model you needed. 293 | 294 | Internally the `DataPrimitiveProcessor#readPrimitive` or `DataPrimitiveValueProcessor#readPrimitive` implementation of the OData service will be invoked. 295 | 296 | Request: 297 | 298 | ``` 299 | PATH: /BookSet(ID_OF_THE_BOOK)/PROPERTY_NAME 300 | METHOD: GET 301 | Header (optional): Accept: application/json;odata.metadata=minimal 302 | ``` 303 | 304 | **Example** 305 | 306 | ``` 307 | http://localhost:8080/odatav4-javaee-example-apache-olingo/api/servlet/v1/odatademo.svc/BookSet(1)/title 308 | ``` 309 | 310 | Result: 311 | 312 | The expected result is a response with the title value of the book with the id 1. 313 | 314 | ```json 315 | { 316 | "@odata.context": "$metadata#BookSet/title", 317 | "value": "Book Title 1" 318 | } 319 | ``` 320 | 321 | ### Plain Text Value 322 | 323 | If you use the `DataPrimitiveValueProcessor#readPrimitiveValue` implementation, it is also possible to request only the pure plain text value of a property. 324 | 325 | Request: 326 | 327 | ``` 328 | PATH: /BookSet(ID_OF_THE_BOOK)/PROPERTY_NAME/$value 329 | METHOD: GET 330 | Header (optional): Accept: application/json;odata.metadata=minimal 331 | ``` 332 | 333 | **Example** 334 | 335 | ``` 336 | http://localhost:8080/odatav4-javaee-example-apache-olingo/api/servlet/v1/odatademo.svc/BookSet(1)/title/$value 337 | ``` 338 | 339 | Result: 340 | 341 | The expected result is a response with only the pure plain text value of the title property. 342 | 343 | ```text 344 | Book Title 1 345 | ``` 346 | 347 | ## Request: Create New Book Or Author 348 | 349 | With this request we can create a new book (with author data) or a new author and add it to the available list. 350 | The Olingo library takes this request, serializes the request body and invokes the corresponding method of our processor class. 351 | 352 | Internally the `DataEntityProcessor#createEntity` implementation of the OData service will be invoked. 353 | 354 | Request: 355 | 356 | ``` 357 | PATH: /BookSet or /AuthorSet 358 | METHOD: POST 359 | Header: Content-Type: application/json;odata.metadata=minimal 360 | Body: JSON data 361 | ``` 362 | 363 | **Example - New Book** 364 | 365 | ``` 366 | http://localhost:8080/odatav4-javaee-example-apache-olingo/api/servlet/v1/odatademo.svc/BookSet 367 | ``` 368 | 369 | Request Body: 370 | 371 | ```json 372 | { 373 | "title": "Book Title New", 374 | "description": "This is the description of the new book", 375 | "releaseDate": "2017-04-21", 376 | "author": { 377 | "name": "Author New", 378 | "gender": "FEMALE" 379 | }, 380 | "price": 11.95, 381 | "inStock": true 382 | } 383 | ``` 384 | 385 | Result: 386 | 387 | The result is a response with the details of the new book with the new assigned id. 388 | 389 | ```json 390 | { 391 | "@odata.context": "$metadata#BookSet", 392 | "title": "Book Title New", 393 | "description": "This is the description of the new book", 394 | "releaseDate": "2017-04-21", 395 | "price": 11.95, 396 | "inStock": true, 397 | "id": 4 398 | } 399 | ``` 400 | 401 | 402 | **Example - New Author** 403 | 404 | ``` 405 | http://localhost:8080/odatav4-javaee-example-apache-olingo/api/servlet/v1/odatademo.svc/AuthorSet(1) 406 | ``` 407 | 408 | Request Body: 409 | 410 | ```json 411 | { 412 | "name": "Author 5 New", 413 | "gender": "MALE", 414 | "contactInfo": { 415 | "eMail": "author5@test.xyz", 416 | "phone": "111/111" 417 | } 418 | } 419 | ``` 420 | 421 | Result: 422 | 423 | The result is a response with the details of the new author with the new assigned id. 424 | 425 | ```json 426 | { 427 | "@odata.context": "$metadata#AuthorSet", 428 | "name": "Author 5 New", 429 | "gender": "MALE", 430 | "contactInfo": 431 | { 432 | "eMail": "author5@test.xyz", 433 | "phone": "111/111" 434 | }, 435 | "id": 6 436 | } 437 | ``` 438 | 439 | ## Request: Update Existing Book Or Author 440 | 441 | With this request we can update the values of an existing book or author. The update of an entity can be realized either with a `PUT` or a `PATCH` request. 442 | 443 | **PUT**: The value of the property is updated in the backend. The value of the other properties is set to null (exception: key properties can never be null). 444 | 445 | **PATCH**: The value of the property is updated in the backend. The values of the other properties remain untouched. 446 | 447 | The difference becomes relevant only in case if the user doesn’t send all the properties in the request body. 448 | 449 | Internally the `DataEntityProcessor#updateEntity` implementation of the OData service will be invoked. 450 | 451 | Request: 452 | 453 | ``` 454 | PATH: /BookSet(ID_OF_THE_BOOK) or /AuthorSet(ID_OF_THE_AUTHOR) 455 | METHOD: PUT or PATCH 456 | Header: Content-Type: application/json;odata.metadata=minimal 457 | Body: JSON data 458 | ``` 459 | 460 | **Example** 461 | 462 | ``` 463 | http://localhost:8080/odatav4-javaee-example-apache-olingo/api/servlet/v1/odatademo.svc/BookSet(2) 464 | ``` 465 | 466 | Request Body: 467 | 468 | ```json 469 | { 470 | "title": "Book Title 2 Updated", 471 | "description": "This is the description of book 2 Updated" 472 | } 473 | ``` 474 | 475 | Result: 476 | 477 | The OData service is not expected to return any response payload (HTTP status code to 204 – no content). 478 | 479 | 480 | ## Request: Delete Existing Book Or Author 481 | 482 | With this request we can remove data record of an existing book or author. 483 | 484 | Internally the `DataEntityProcessor#deleteEntity` implementation of the OData service will be invoked. 485 | 486 | Request: 487 | 488 | ``` 489 | PATH: /BookSet(ID_OF_THE_BOOK) or /AuthorSet(ID_OF_THE_AUTHOR) 490 | METHOD: DELETE 491 | ``` 492 | 493 | **Example** 494 | 495 | ``` 496 | http://localhost:8080/odatav4-javaee-example-apache-olingo/api/servlet/v1/odatademo.svc/BookSet(2) 497 | ``` 498 | 499 | Result: 500 | 501 | The OData service is not expected to return any response payload (HTTP status code to 204 – no content). 502 | 503 | 504 | ## Request: Count Number Of All Books Or Authors Data 505 | 506 | To request only the number of items of a collection of entities or items of a collection-valued property, create a GET request with `/$count` appended to the resource path of the collection. On success, the response body MUST contain the exact count of items. 507 | 508 | Internally the `DataCollectionProcessor#countEntityCollection` implementation of the OData service will be invoked. 509 | 510 | 511 | Request: 512 | 513 | ``` 514 | PATH: /BookSet/$count or /AuthorSet/$count 515 | METHOD: GET 516 | ``` 517 | 518 | **Example** 519 | 520 | ``` 521 | http://localhost:8080/odatav4-javaee-example-apache-olingo/api/servlet/v1/odatademo.svc/BookSet/$count 522 | ``` 523 | 524 | Result: 525 | 526 | The result is a response with the count number of all books. 527 | 528 | ---------------------------------- 529 | Markus Eschenbach 530 | [www.blogging-it.com](http://www.blogging-it.com) 531 | 532 | -------------------------------------------------------------------------------- /etc/docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #=================================================================== 4 | # 5 | # -- VERSION 1.0.0 -- 6 | # 7 | # Startparameter: 8 | # build | start | startbg | stop | remove | login 9 | # 10 | # Example command: 11 | # ./docker.sh build 12 | # 13 | # ::::::::::::::: www.blogging-it.com ::::::::::::::: 14 | # 15 | # Copyright (C) 2017 Markus Eschenbach. All rights reserved. 16 | # 17 | # 18 | # This software is provided on an "as-is" basis, without any express or implied warranty. 19 | # In no event shall the author be held liable for any damages arising from the 20 | # use of this software. 21 | # 22 | # Permission is granted to anyone to use this software for any purpose, 23 | # including commercial applications, and to alter and redistribute it, 24 | # provided that the following conditions are met: 25 | # 26 | # 1. All redistributions of source code files must retain all copyright 27 | # notices that are currently in place, and this list of conditions without 28 | # modification. 29 | # 30 | # 2. All redistributions in binary form must retain all occurrences of the 31 | # above copyright notice and web site addresses that are currently in 32 | # place (for example, in the About boxes). 33 | # 34 | # 3. The origin of this software must not be misrepresented; you must not 35 | # claim that you wrote the original software. If you use this software to 36 | # distribute a product, an acknowledgment in the product documentation 37 | # would be appreciated but is not required. 38 | # 39 | # 4. Modified versions in source or binary form must be plainly marked as 40 | # such, and must not be misrepresented as being the original software. 41 | # 42 | # ::::::::::::::: www.blogging-it.com ::::::::::::::: 43 | #=================================================================== 44 | 45 | 46 | #=================================================================== 47 | # SETTINGS 48 | #=================================================================== 49 | BASE_DIR="$(dirname $0)" 50 | SCRIPT_DIR="$( cd -P -- "$(dirname -- "$(command -v -- "$0")")" && pwd -P )" 51 | WORK_DIR="$SCRIPT_DIR/.." 52 | WAR_APP_PATH="$WORK_DIR/module-application/target/angular2-qickstart-javaee7-application.war" 53 | WAR_CLIENT_PATH="$WORK_DIR/module-client-web/target/angular2-qickstart-javaee7-client-web.war" 54 | 55 | DOCKER_VERSION="1.0.0" 56 | DOCKER_EXEC="docker" 57 | DOCKER_IMAGE="angular2/javaee7-quickstart" 58 | DOCKER_NAME="angular2-javaee7-quickstart" 59 | DOCKER_PORT="8080:8080" 60 | 61 | ACTION="$1" 62 | 63 | #=================================================================== 64 | # FUNCTIONS 65 | #=================================================================== 66 | 67 | function isAppInstalled { 68 | local isInst_=1 69 | type $1 >/dev/null 2>&1 || { local isInst_=0; } # set to 0 if not found 70 | echo "$isInst_" 71 | } 72 | 73 | 74 | #=================================================================== 75 | # MAIN 76 | #=================================================================== 77 | 78 | valid=true 79 | 80 | if [[ $(isAppInstalled docker) != 1 ]]; then 81 | echo "ERROR: Please install 'docker' to execute this script" 82 | valid=false 83 | fi 84 | 85 | if [ ! -f "$WAR_APP_PATH" ]; then 86 | echo "ERROR: file '$WAR_APP_PATH' missing! Please execute maven build..." 87 | valid=false 88 | fi 89 | 90 | if [ ! -f "$WAR_CLIENT_PATH" ]; then 91 | echo "ERROR: file '$WAR_CLIENT_PATH' missing! Please execute maven build..." 92 | valid=false 93 | fi 94 | 95 | if [ "$valid" = false ] ; then 96 | exit 1 97 | fi 98 | 99 | 100 | EXEC_CMD="" 101 | case "$ACTION" in 102 | build) 103 | EXEC_CMD="$DOCKER_EXEC build -t $DOCKER_IMAGE:$DOCKER_VERSION ." 104 | ;; 105 | 106 | start) 107 | EXEC_CMD="$DOCKER_EXEC run -P -it --rm -p $DOCKER_PORT --name $DOCKER_NAME $DOCKER_IMAGE:$DOCKER_VERSION" 108 | ;; 109 | 110 | startbg) 111 | EXEC_CMD="$DOCKER_EXEC run -P -d -it --rm -p $DOCKER_PORT --name $DOCKER_NAME $DOCKER_IMAGE:$DOCKER_VERSION" 112 | ;; 113 | 114 | stop) 115 | EXEC_CMD="$DOCKER_EXEC stop $DOCKER_NAME" 116 | ;; 117 | 118 | remove) 119 | EXEC_CMD="$DOCKER_EXEC rm -f $DOCKER_NAME ; docker rmi -f $DOCKER_IMAGE:$DOCKER_VERSION" 120 | ;; 121 | 122 | login) 123 | EXEC_CMD="$DOCKER_EXEC exec -i -t $DOCKER_NAME /bin/bash" 124 | ;; 125 | 126 | *) 127 | echo $"Usage: $ACTION {build|start|startbg|stop|remove|login}" 128 | exit 1 129 | esac 130 | 131 | cd "$WORK_DIR" 132 | 133 | echo "Run '$EXEC_CMD'" 134 | eval $EXEC_CMD 135 | 136 | 137 | exit 0 -------------------------------------------------------------------------------- /etc/entity-data-model-structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mesche/ODataV4-JavaEE-Example-Apache-Olingo/ee450a23407ca78c205e838f077a6f87677bf761/etc/entity-data-model-structure.png -------------------------------------------------------------------------------- /nb-configuration.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 16 | ide 17 | 18 | 19 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 4.0.0 7 | 8 | com.bloggingit 9 | odatav4-javaee-example-apache-olingo 10 | 1.0.0-SNAPSHOT 11 | 12 | war 13 | 14 | ODataV4-JavaEE-Example-Apache-Olingo 15 | 16 | 17 | ${project.build.directory}/endorsed 18 | 19 | UTF-8 20 | 1.8 21 | 1.8 22 | 23 | ${project.basedir} 24 | 25 | 4.12 26 | 2.10.4 27 | 3.6.1 28 | 3.0.2 29 | 3.0.0 30 | 31 | 7.0 32 | 4.3.0 33 | 34 | 35 | 36 | 37 | 38 | javax 39 | javaee-api 40 | ${version.javaee.api} 41 | provided 42 | 43 | 44 | 45 | 46 | org.projectlombok 47 | lombok 48 | 1.16.16 49 | 50 | 51 | 52 | 53 | 54 | org.apache.commons 55 | commons-lang3 56 | 3.5 57 | 58 | 59 | 60 | commons-io 61 | commons-io 62 | 2.5 63 | 64 | 65 | 66 | 67 | 68 | org.apache.olingo 69 | odata-server-api 70 | ${odata.version} 71 | 72 | 73 | org.apache.olingo 74 | odata-server-core 75 | ${odata.version} 76 | 77 | 78 | 79 | org.apache.olingo 80 | odata-commons-api 81 | ${odata.version} 82 | 83 | 84 | org.apache.olingo 85 | odata-commons-core 86 | ${odata.version} 87 | 88 | 89 | 90 | 91 | org.reflections 92 | reflections 93 | 0.9.11 94 | 95 | 96 | 97 | 98 | 99 | junit 100 | junit 101 | ${version.test.junit} 102 | test 103 | 104 | 105 | 106 | 107 | 108 | ${project.artifactId} 109 | 110 | 111 | 112 | 113 | org.apache.maven.plugins 114 | maven-resources-plugin 115 | ${version.maven.resources-plugin} 116 | 117 | 118 | 119 | org.apache.maven.plugins 120 | maven-compiler-plugin 121 | ${version.maven.compiler-plugin} 122 | 123 | true 124 | true 125 | true 126 | 127 | ${endorsed.dir} 128 | 129 | 130 | 131 | 132 | 133 | org.apache.maven.plugins 134 | maven-war-plugin 135 | ${version.maven.war-plugin} 136 | 137 | false 138 | 139 | true 140 | 141 | 142 | 143 | 144 | ${project.name} 145 | ${project.version} 146 | 147 | 148 | true 149 | classes 150 | 151 | 152 | 153 | 154 | 155 | org.apache.maven.plugins 156 | maven-javadoc-plugin 157 | ${version.reporting.javadoc} 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | org.apache.maven.plugins 171 | maven-javadoc-plugin 172 | ${version.reporting.javadoc} 173 | 174 | 175 | 176 | 177 | -------------------------------------------------------------------------------- /src/main/java/com/bloggingit/odata/edm/annotation/EdmComplexType.java: -------------------------------------------------------------------------------- 1 | package com.bloggingit.odata.edm.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * 10 | * Annotation for a EDM/CSDL ComplexType element. 11 | *
12 | * EdmComplexType holds a set of related information like EdmPrimitiveType 13 | * properties and EdmComplexType properties. 14 | */ 15 | @Retention(RetentionPolicy.RUNTIME) 16 | @Target(ElementType.TYPE) 17 | public @interface EdmComplexType { 18 | 19 | /** 20 | * Define the name for the ComplexType. If not set a default value has to be 21 | * generated by the EDM provider. 22 | * 23 | * @return name for the ComplexType 24 | */ 25 | String name() default ""; 26 | 27 | /** 28 | * Define the namespace for the ComplexType. If not set a default value has 29 | * to be generated by the EDM provider. 30 | * 31 | * @return namespace for the ComplexType 32 | */ 33 | String namespace() default ""; 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/bloggingit/odata/edm/annotation/EdmEntitySet.java: -------------------------------------------------------------------------------- 1 | package com.bloggingit.odata.edm.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Annotation for a EDM/CSDL EntitySet element. 10 | *

11 | * EdmEntitySet is the container for entity type instances as described in the 12 | * OData protocol. The {@link EdmEntitySet} annotation defines the annotated 13 | * class as entity set and must be used in conjunction with an 14 | * {@link EdmEntityType} annotation on a class. 15 | *

16 | */ 17 | @Retention(RetentionPolicy.RUNTIME) 18 | @Target(ElementType.TYPE) 19 | public @interface EdmEntitySet { 20 | 21 | /** 22 | * Define the name for the EDM Entity Set. If not set a default value has to 23 | * be generated by the EDM provider. 24 | * 25 | * @return name for the EDM Entity Set 26 | */ 27 | String name() default ""; 28 | 29 | /** 30 | * Define the container name for the EDM Entity Set. If not set a default 31 | * value has to be generated by the EDM provider. 32 | * 33 | * @return container name for the EDM Entity Set 34 | */ 35 | String container() default ""; 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/bloggingit/odata/edm/annotation/EdmEntityType.java: -------------------------------------------------------------------------------- 1 | package com.bloggingit.odata.edm.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Annotation for a EDM/CSDL EntityType element. 10 | *

11 | * EdmEntityType holds a set of related information like EdmPrimitiveType, 12 | * EdmComplexType and EdmNavigation properties. 13 | *

14 | */ 15 | @Retention(RetentionPolicy.RUNTIME) 16 | @Target(ElementType.TYPE) 17 | public @interface EdmEntityType { 18 | 19 | /** 20 | * Define the name for the EDM EntityType. If not set a default value has to 21 | * be generated by the EDM provider. 22 | * 23 | * @return name for the EDM EntityType 24 | */ 25 | String name() default ""; 26 | 27 | /** 28 | * Define the namespace for the EDM EntityType. If not set a default value 29 | * has to be generated by the EDM provider. 30 | * 31 | * @return namespace for the EDM EntityType 32 | */ 33 | String namespace() default ""; 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/bloggingit/odata/edm/annotation/EdmFacets.java: -------------------------------------------------------------------------------- 1 | package com.bloggingit.odata.edm.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | *

10 | * Annotation for definition of EdmFactes on an EdmProperty (for an 11 | * EdmEntityType or EdmComplexType which contains the EdmProperty as a 12 | * field). 13 | *

14 | */ 15 | @Retention(RetentionPolicy.RUNTIME) 16 | @Target(ElementType.ANNOTATION_TYPE) 17 | public @interface EdmFacets { 18 | 19 | /** 20 | * The maximum length of the type in use. A negative value indicates for the 21 | * EDM provider an unset/default value. 22 | * 23 | * @return the maximum length of the type in use as Integer 24 | */ 25 | int maxLength() default -1; 26 | 27 | /** 28 | * The scale of the type in use. A negative value indicates for the EDM 29 | * provider an unset/default value. 30 | * 31 | * @return the scale of the type in use as Integer 32 | */ 33 | int scale() default -1; 34 | 35 | /** 36 | * The precision of the type in use. A negative value indicates for the EDM 37 | * provider an unset/default value. 38 | * 39 | * @return the precision of the type in use as Integer 40 | */ 41 | int precision() default -1; 42 | 43 | /** 44 | * The information if the type in use is nullable. The default value for 45 | * nullable is true. 46 | * 47 | * @return true if the type in use is nullable, 48 | * false otherwise. 49 | */ 50 | boolean nullable() default true; 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/bloggingit/odata/edm/annotation/EdmKey.java: -------------------------------------------------------------------------------- 1 | package com.bloggingit.odata.edm.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Annotation for definition of an EdmProperty as EdmKey for the EdmEntityType 10 | * which contains the EdmProperty. 11 | *

12 | * This annotation can not be parameterized, all values like name are defined 13 | * via the {@link EdmProperty} annotation. In addition the EdmKey annotation has 14 | * to be used in conjunction with an EdmProperty annotation on a field within a 15 | * EdmEntityType annotated class. 16 | *

17 | */ 18 | @Retention(RetentionPolicy.RUNTIME) 19 | @Target(ElementType.FIELD) 20 | public @interface EdmKey { 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/bloggingit/odata/edm/annotation/EdmProperty.java: -------------------------------------------------------------------------------- 1 | package com.bloggingit.odata.edm.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Annotation for definition of an EdmProperty for an EdmEntityType or 10 | * EdmComplexType which contains the EdmProperty as a field. 11 | *

12 | * The EdmProperty annotation has to be used on a field within a EdmEntityType 13 | * or EdmComplexType annotated class. 14 | *

15 | */ 16 | @Retention(RetentionPolicy.RUNTIME) 17 | @Target(ElementType.FIELD) 18 | public @interface EdmProperty { 19 | 20 | /** 21 | * Define the name for the Property. If not set a default value has to be 22 | * generated by the EDM provider. 23 | * 24 | * @return name for the Property 25 | */ 26 | String name() default ""; 27 | 28 | /** 29 | * Define the EdmFacets for the Property in the EDM. If not set the default 30 | * EdmFacet values are used (see {@link EdmFacets}). 31 | * 32 | * @return facets for the Property as used in the EDM 33 | */ 34 | EdmFacets facets() default @EdmFacets; 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/bloggingit/odata/edm/enumeration/EdmValueType.java: -------------------------------------------------------------------------------- 1 | package com.bloggingit.odata.edm.enumeration; 2 | 3 | import com.bloggingit.odata.edm.annotation.EdmComplexType; 4 | import com.bloggingit.odata.edm.annotation.EdmEntityType; 5 | import java.lang.reflect.Field; 6 | import org.apache.commons.lang3.ClassUtils; 7 | 8 | /** 9 | * Defines the type of a value (see Valuable). 10 | */ 11 | public enum EdmValueType { 12 | /** 13 | * Primitive type 14 | */ 15 | PRIMITIVE, 16 | /** 17 | * Enum type 18 | */ 19 | ENUM, 20 | /** 21 | * Complex type 22 | */ 23 | COMPLEX, 24 | /** 25 | * Entity type 26 | */ 27 | ENTITY; 28 | 29 | 30 | public static EdmValueType valueOf(Field field) { 31 | Class type = field.getType(); 32 | EdmValueType edmValueType; 33 | 34 | if (type.isEnum()) { 35 | edmValueType = ENUM; 36 | } else if (ClassUtils.isPrimitiveOrWrapper(type)) { 37 | edmValueType = PRIMITIVE; 38 | } else if (type.isAnnotationPresent(EdmComplexType.class)) { 39 | edmValueType = COMPLEX; 40 | } else if (type.isAnnotationPresent(EdmEntityType.class)) { 41 | edmValueType = ENTITY; 42 | } else { // fallback for String, Date, etc. 43 | edmValueType = PRIMITIVE; 44 | } 45 | 46 | return edmValueType; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/bloggingit/odata/exception/EntityDataException.java: -------------------------------------------------------------------------------- 1 | package com.bloggingit.odata.exception; 2 | 3 | /** 4 | * Exception thrown when an error occurs while accessing a data source. 5 | */ 6 | public class EntityDataException extends Exception { 7 | 8 | private static final long serialVersionUID = 1L; 9 | 10 | public EntityDataException(String message) { 11 | super(message); 12 | } 13 | 14 | public EntityDataException(String message, Throwable cause) { 15 | super(message, cause); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/bloggingit/odata/model/Author.java: -------------------------------------------------------------------------------- 1 | package com.bloggingit.odata.model; 2 | 3 | import com.bloggingit.odata.edm.annotation.EdmEntitySet; 4 | import com.bloggingit.odata.edm.annotation.EdmEntityType; 5 | import com.bloggingit.odata.edm.annotation.EdmFacets; 6 | import com.bloggingit.odata.edm.annotation.EdmProperty; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Builder; 9 | import lombok.Getter; 10 | import lombok.NoArgsConstructor; 11 | import lombok.Setter; 12 | 13 | @Getter 14 | @Setter 15 | @NoArgsConstructor 16 | @AllArgsConstructor 17 | @Builder 18 | 19 | @EdmEntityType 20 | @EdmEntitySet 21 | public class Author extends BaseEntity { 22 | 23 | @EdmProperty(facets = @EdmFacets(nullable = false)) 24 | private String name; 25 | 26 | @EdmProperty(facets = @EdmFacets(nullable = false)) 27 | private Gender gender; 28 | 29 | @EdmProperty(facets = @EdmFacets(nullable = false)) 30 | private ContactInfo contactInfo; 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/bloggingit/odata/model/BaseEntity.java: -------------------------------------------------------------------------------- 1 | package com.bloggingit.odata.model; 2 | 3 | import com.bloggingit.odata.edm.annotation.EdmFacets; 4 | import com.bloggingit.odata.edm.annotation.EdmKey; 5 | import com.bloggingit.odata.edm.annotation.EdmProperty; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Getter; 8 | import lombok.NoArgsConstructor; 9 | import lombok.Setter; 10 | 11 | 12 | @Getter 13 | @Setter 14 | @NoArgsConstructor 15 | @AllArgsConstructor 16 | public abstract class BaseEntity { 17 | 18 | @EdmKey 19 | @EdmProperty(facets = @EdmFacets(nullable = false)) 20 | private long id; 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/bloggingit/odata/model/Book.java: -------------------------------------------------------------------------------- 1 | package com.bloggingit.odata.model; 2 | 3 | import com.bloggingit.odata.edm.annotation.EdmEntitySet; 4 | import com.bloggingit.odata.edm.annotation.EdmEntityType; 5 | import com.bloggingit.odata.edm.annotation.EdmFacets; 6 | import com.bloggingit.odata.edm.annotation.EdmProperty; 7 | import java.util.Date; 8 | import lombok.AllArgsConstructor; 9 | import lombok.Builder; 10 | import lombok.Getter; 11 | import lombok.NoArgsConstructor; 12 | import lombok.Setter; 13 | 14 | @Getter 15 | @Setter 16 | @NoArgsConstructor 17 | @AllArgsConstructor 18 | @Builder 19 | 20 | @EdmEntityType 21 | @EdmEntitySet 22 | public class Book extends BaseEntity { 23 | 24 | @EdmProperty(facets = @EdmFacets(nullable = false)) 25 | private String title; 26 | 27 | @EdmProperty(facets = @EdmFacets(maxLength = 2000)) 28 | private String description; 29 | 30 | @EdmProperty(name = "releaseDate") 31 | private Date release; 32 | 33 | @EdmProperty 34 | private Author author; 35 | 36 | @EdmProperty 37 | private Double price; 38 | 39 | @EdmProperty 40 | private boolean inStock; 41 | 42 | public void setRelease(Date release) { 43 | this.release = (release != null) ? new Date(release.getTime()) : null; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/bloggingit/odata/model/ContactInfo.java: -------------------------------------------------------------------------------- 1 | package com.bloggingit.odata.model; 2 | 3 | import com.bloggingit.odata.edm.annotation.EdmComplexType; 4 | import com.bloggingit.odata.edm.annotation.EdmFacets; 5 | import com.bloggingit.odata.edm.annotation.EdmProperty; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Builder; 8 | import lombok.Getter; 9 | import lombok.NoArgsConstructor; 10 | import lombok.Setter; 11 | 12 | @Getter 13 | @Setter 14 | @NoArgsConstructor 15 | @AllArgsConstructor 16 | @Builder 17 | 18 | @EdmComplexType 19 | public class ContactInfo { 20 | 21 | @EdmProperty(facets = @EdmFacets(nullable = false)) 22 | private String eMail; 23 | 24 | @EdmProperty 25 | private String phone; 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/bloggingit/odata/model/Gender.java: -------------------------------------------------------------------------------- 1 | package com.bloggingit.odata.model; 2 | 3 | /** 4 | * 5 | * @author mes 6 | */ 7 | public enum Gender { 8 | 9 | MALE, FEMALE, UNKOWN 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/bloggingit/odata/olingo/edm/meta/EntityMetaData.java: -------------------------------------------------------------------------------- 1 | package com.bloggingit.odata.olingo.edm.meta; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import lombok.Getter; 6 | 7 | /** 8 | * 9 | * This class contains the meta data of a entity for the OData service. 10 | * 11 | * @param the generic type of the entity data 12 | */ 13 | @Getter 14 | public class EntityMetaData { 15 | 16 | /** 17 | * The suffix for a OData typeset 18 | */ 19 | private static final String TYPE_SET_SUFFIX = "Set"; 20 | 21 | /** 22 | * The corresponding entity class 23 | */ 24 | private final Class entityClass; 25 | 26 | /** 27 | * The name of the entity typeset. 28 | */ 29 | private final String entityTypeSetName; 30 | 31 | /** 32 | * The name of the entity type. 33 | */ 34 | private final String entityTypeName; 35 | 36 | private final boolean isEntitySet; 37 | 38 | private final boolean isComplexType; 39 | /** 40 | * Contains the list of all properties meta data. 41 | */ 42 | private final List properties; 43 | 44 | public EntityMetaData(Class entityClass, String serviceNamespace, boolean isEntitySet, boolean isComplexType, List properties) { 45 | this.entityClass = entityClass; 46 | 47 | //generate values 48 | this.entityTypeName = this.entityClass.getSimpleName(); 49 | this.isEntitySet = isEntitySet; 50 | this.isComplexType = isComplexType; 51 | 52 | this.entityTypeSetName = (isEntitySet) ? this.entityTypeName + TYPE_SET_SUFFIX : null; 53 | 54 | this.properties = properties; 55 | } 56 | 57 | public List getEnumPropertyData() { 58 | List enumPropertyList = new ArrayList<>(); 59 | 60 | this.properties.stream().filter((prop) -> (prop instanceof EntityMetaPropertyEnum)).forEachOrdered((prop) -> { 61 | enumPropertyList.add(prop); 62 | }); 63 | return enumPropertyList; 64 | } 65 | 66 | public List getInlineEntityPropertyData() { 67 | List inlinePropertyList = new ArrayList<>(); 68 | 69 | this.properties.stream().filter((prop) -> (prop instanceof EntityMetaPropertyEntity)).forEachOrdered((prop) -> { 70 | inlinePropertyList.add((EntityMetaPropertyEntity) prop); 71 | }); 72 | return inlinePropertyList; 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/com/bloggingit/odata/olingo/edm/meta/EntityMetaDataBuilder.java: -------------------------------------------------------------------------------- 1 | package com.bloggingit.odata.olingo.edm.meta; 2 | 3 | import com.bloggingit.odata.edm.annotation.EdmComplexType; 4 | import com.bloggingit.odata.edm.annotation.EdmEntitySet; 5 | import com.bloggingit.odata.edm.annotation.EdmEntityType; 6 | import com.bloggingit.odata.edm.annotation.EdmKey; 7 | import com.bloggingit.odata.edm.annotation.EdmProperty; 8 | import com.bloggingit.odata.edm.enumeration.EdmValueType; 9 | import com.bloggingit.odata.olingo.v4.util.ReflectionUtils; 10 | import java.lang.annotation.Annotation; 11 | import java.lang.reflect.Field; 12 | import java.util.ArrayList; 13 | import java.util.HashSet; 14 | import java.util.List; 15 | import java.util.Set; 16 | import java.util.stream.Stream; 17 | import org.apache.commons.lang3.StringUtils; 18 | import org.apache.commons.lang3.reflect.FieldUtils; 19 | 20 | /** 21 | * 22 | * @author mes 23 | */ 24 | public class EntityMetaDataBuilder { 25 | 26 | private static final Class EDM_ENTITY_SET = EdmEntitySet.class; 27 | private static final Class EDM_ENTITY_TYPE = EdmEntityType.class; 28 | private static final Class EDM_COMPLEX_TYPE = EdmComplexType.class; 29 | private static final Class EDM_PROPERTY = EdmProperty.class; 30 | private static final Class EDM_KEY = EdmKey.class; 31 | 32 | public static EntityMetaDataContainer createContainer(String serviceNamespace, String edmContainer, String dtoPackage) { 33 | 34 | Set> metaEntities = createEntityMetaDataList(dtoPackage, serviceNamespace); 35 | 36 | return new EntityMetaDataContainer(serviceNamespace, edmContainer, metaEntities); 37 | } 38 | 39 | private static Set> createEntityMetaDataList(String dtoPackage, String serviceNamespace) { 40 | Set> metaEntities = new HashSet<>(); 41 | 42 | Set> odataEntityClasses = ReflectionUtils.findClassesInPackageAnnotatedWith(dtoPackage, EDM_ENTITY_TYPE); 43 | Set> odataComplexClasses = ReflectionUtils.findClassesInPackageAnnotatedWith(dtoPackage, EDM_COMPLEX_TYPE); 44 | 45 | Stream> odataAllClasses = Stream.concat(odataEntityClasses.stream(), odataComplexClasses.stream()); 46 | 47 | odataAllClasses.map((odataClass) -> { 48 | return createEntityMetaDataFromClass(odataClass, serviceNamespace); 49 | }).forEachOrdered((entity) -> { 50 | metaEntities.add(entity); 51 | }); 52 | 53 | return metaEntities; 54 | } 55 | 56 | private static EntityMetaData createEntityMetaDataFromClass(Class entityClass, String serviceNamespace) { 57 | boolean isEntitySet = entityClass.isAnnotationPresent(EDM_ENTITY_SET); 58 | boolean isComplexType = entityClass.isAnnotationPresent(EDM_COMPLEX_TYPE); 59 | List properties = createEntityPropertiesFromClass(entityClass, serviceNamespace); 60 | return new EntityMetaData<>(entityClass, serviceNamespace, isEntitySet, isComplexType, properties); 61 | } 62 | 63 | 64 | private static List createEntityPropertiesFromClass(Class entityClass, String serviceNamespace) { 65 | List newProperties = new ArrayList<>(); 66 | 67 | Field[] fields = FieldUtils.getFieldsWithAnnotation(entityClass, EDM_PROPERTY); 68 | 69 | for (Field field : fields) { 70 | 71 | EdmValueType edmValueType = EdmValueType.valueOf(field); 72 | 73 | Class fieldTypeClass = field.getType(); 74 | 75 | EdmProperty edmProperty = (EdmProperty) field.getAnnotation(EDM_PROPERTY); 76 | 77 | String edmName = (StringUtils.isNotBlank(edmProperty.name())) ? edmProperty.name() : field.getName(); 78 | 79 | EntityMetaProperty propertyData = null; 80 | 81 | boolean isKey = (field.isAnnotationPresent(EDM_KEY)); 82 | 83 | if (null != edmValueType) 84 | switch (edmValueType) { 85 | case PRIMITIVE: 86 | propertyData = EntityMetaPropertyPrimitve 87 | .builder() 88 | .isKey(isKey) 89 | .name(edmName) 90 | .fieldName(field.getName()) 91 | .fieldType(fieldTypeClass) 92 | .maxLength(edmProperty.facets().maxLength()) 93 | .nullable(edmProperty.facets().nullable()) 94 | .precision(edmProperty.facets().precision()) 95 | .scale(edmProperty.facets().scale()) 96 | .build(); 97 | break; 98 | case ENUM: 99 | propertyData = EntityMetaPropertyEnum 100 | .builder() 101 | .isKey(isKey) 102 | .name(edmName) 103 | .fieldName(field.getName()) 104 | .fieldType(fieldTypeClass) 105 | .build(); 106 | break; 107 | case COMPLEX: 108 | EntityMetaData valueComplexMetaData = createEntityMetaDataFromClass(fieldTypeClass, serviceNamespace); 109 | propertyData = EntityMetaPropertyComplex 110 | .builder() 111 | .name(edmName) 112 | .fieldName(field.getName()) 113 | .fieldType(fieldTypeClass) 114 | .valueMetaData(valueComplexMetaData) 115 | .build(); 116 | break; 117 | case ENTITY: 118 | EntityMetaData valueEntityMetaData = createEntityMetaDataFromClass(fieldTypeClass, serviceNamespace); 119 | propertyData = EntityMetaPropertyEntity 120 | .builder() 121 | .name(edmName) 122 | .fieldName(field.getName()) 123 | .fieldType(fieldTypeClass) 124 | .valueMetaData(valueEntityMetaData) 125 | .build(); 126 | break; 127 | default: 128 | break; 129 | } 130 | 131 | newProperties.add(propertyData); 132 | } 133 | 134 | return newProperties; 135 | } 136 | 137 | } 138 | -------------------------------------------------------------------------------- /src/main/java/com/bloggingit/odata/olingo/edm/meta/EntityMetaDataContainer.java: -------------------------------------------------------------------------------- 1 | package com.bloggingit.odata.olingo.edm.meta; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | import java.util.Set; 6 | import lombok.Getter; 7 | 8 | /** 9 | * 10 | * This class holds all meta data of multiple entities. 11 | */ 12 | public class EntityMetaDataContainer { 13 | 14 | @Getter 15 | private final String serviceNamespace; 16 | 17 | @Getter 18 | private final String edmContainerName; 19 | 20 | /** 21 | * List of meta data for multiple entities. 22 | */ 23 | @Getter 24 | private final Set> allEntityMetaData; 25 | 26 | public EntityMetaDataContainer(String serviceNamespace, String edmContainerName, Set> allEntityMetaData) { 27 | this.serviceNamespace = serviceNamespace; 28 | this.edmContainerName = edmContainerName; 29 | this.allEntityMetaData = Collections.synchronizedSet(allEntityMetaData); 30 | } 31 | 32 | /** 33 | * Returns the meta entity data of the given type set name. If no set with 34 | * this name found, the method returns null. 35 | * 36 | * @param entityTypeSetName the name of the type set 37 | * @return the meta data of the entity or null if no set found 38 | */ 39 | public EntityMetaData getEntityMetaDataByTypeSetName(String entityTypeSetName) { 40 | EntityMetaData entity = null; 41 | for (EntityMetaData meta : this.allEntityMetaData) { 42 | if (meta.getEntityTypeSetName() != null && meta.getEntityTypeSetName().equals(entityTypeSetName)) { 43 | entity = meta; 44 | break; 45 | } 46 | } 47 | return entity; 48 | } 49 | 50 | /** 51 | * Returns the meta entity data of the given full qualified type name. If no 52 | * meta data with this name found, the method returns null. 53 | * 54 | * @param serviceNamespace the service namespace 55 | * @param entityTypeName the name of the type name 56 | * @return the meta data of the entity or null if no meta data 57 | * found 58 | */ 59 | public EntityMetaData getEntityMetaDataByTypeName(String serviceNamespace, String entityTypeName) { 60 | 61 | EntityMetaData entity = null; 62 | 63 | if (this.serviceNamespace.equals(serviceNamespace)) { 64 | for (EntityMetaData meta : this.allEntityMetaData) { 65 | if (meta.getEntityTypeName().equals(entityTypeName)) { 66 | entity = meta; 67 | break; 68 | } 69 | } 70 | } 71 | return entity; 72 | } 73 | 74 | public EntityMetaProperty getEntityMetaPropertyDataByTypeName(String serviceNamespace, String propertyTypeName) { 75 | EntityMetaProperty propData = null; 76 | if (this.serviceNamespace.equals(serviceNamespace)) { 77 | for (EntityMetaData meta : this.allEntityMetaData) { 78 | List enumPropertyDataList = meta.getEnumPropertyData(); 79 | 80 | for (EntityMetaProperty propEntry : enumPropertyDataList) { 81 | if (propEntry.getFieldType().getSimpleName().equals(propertyTypeName)) { 82 | propData = propEntry; 83 | break; 84 | } 85 | 86 | } 87 | 88 | } 89 | } 90 | return propData; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/com/bloggingit/odata/olingo/edm/meta/EntityMetaProperty.java: -------------------------------------------------------------------------------- 1 | package com.bloggingit.odata.olingo.edm.meta; 2 | 3 | import com.bloggingit.odata.olingo.v4.util.DefaultValue; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | 7 | /** 8 | * This abstract class is for the meta data of an entity property 9 | */ 10 | @Getter 11 | @AllArgsConstructor 12 | public abstract class EntityMetaProperty { 13 | 14 | /** 15 | * true if property is annotated with @EdmKey or false if not 16 | */ 17 | private final boolean isKey; 18 | 19 | /** 20 | * The name of the edm property. 21 | */ 22 | private final String edmName; 23 | 24 | /** 25 | * The property name of the entity. 26 | */ 27 | private final String fieldName; 28 | 29 | /** 30 | * The property type of the entity. 31 | */ 32 | private final Class fieldType; 33 | 34 | /** 35 | * true if nullable or null if not specified 36 | */ 37 | private final Boolean nullable; 38 | 39 | /** 40 | * @return the default value as a String or null if not specified 41 | */ 42 | public Object getDefaultValue() { 43 | return DefaultValue.forClass(this.getFieldType()); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/bloggingit/odata/olingo/edm/meta/EntityMetaPropertyComplex.java: -------------------------------------------------------------------------------- 1 | package com.bloggingit.odata.olingo.edm.meta; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | 6 | /** 7 | * This class contains the meta data of a complex property for the OData 8 | * service. 9 | */ 10 | @Getter 11 | public class EntityMetaPropertyComplex extends EntityMetaProperty { 12 | 13 | /** 14 | * The type of the property value. 15 | */ 16 | private final EntityMetaData valueMetaData; 17 | 18 | @Builder 19 | private EntityMetaPropertyComplex( 20 | String name, 21 | String fieldName, 22 | Class fieldType, 23 | Boolean nullable, 24 | EntityMetaData valueMetaData) { 25 | 26 | super(false, name, fieldName, fieldType, nullable); 27 | this.valueMetaData = valueMetaData; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/bloggingit/odata/olingo/edm/meta/EntityMetaPropertyEntity.java: -------------------------------------------------------------------------------- 1 | package com.bloggingit.odata.olingo.edm.meta; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | 6 | /** 7 | * This class contains the meta data of a entity (e. g. navigation) property for 8 | * the OData service. 9 | */ 10 | @Getter 11 | public class EntityMetaPropertyEntity extends EntityMetaProperty { 12 | 13 | /** 14 | * The type of the property value. 15 | */ 16 | private final EntityMetaData valueMetaData; 17 | 18 | @Builder 19 | private EntityMetaPropertyEntity( 20 | String name, 21 | String fieldName, 22 | Class fieldType, 23 | Boolean nullable, 24 | EntityMetaData valueMetaData) { 25 | 26 | super(false, name, fieldName, fieldType, nullable); 27 | this.valueMetaData = valueMetaData; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/bloggingit/odata/olingo/edm/meta/EntityMetaPropertyEnum.java: -------------------------------------------------------------------------------- 1 | package com.bloggingit.odata.olingo.edm.meta; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | 6 | /** 7 | * This class contains the meta data of an enum property for the OData service. 8 | */ 9 | @Getter 10 | public class EntityMetaPropertyEnum extends EntityMetaProperty { 11 | 12 | @Builder 13 | private EntityMetaPropertyEnum( 14 | boolean isKey, 15 | String name, 16 | String fieldName, 17 | Class fieldType, 18 | Boolean nullable) { 19 | 20 | super(isKey, name, fieldName, fieldType, nullable); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/bloggingit/odata/olingo/edm/meta/EntityMetaPropertyPrimitve.java: -------------------------------------------------------------------------------- 1 | package com.bloggingit.odata.olingo.edm.meta; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | 6 | /** 7 | * This class contains the meta data of a primitive property for the OData 8 | * service. 9 | */ 10 | @Getter 11 | public class EntityMetaPropertyPrimitve extends EntityMetaProperty { 12 | 13 | /** 14 | * The maximum length of the type in use as Integer. 15 | * 16 | * A negative value indicates for the EDM provider an unset/default value. 17 | */ 18 | private final int maxLength; 19 | 20 | /** 21 | * The precision of the type in use as Integer. 22 | * 23 | * A negative value indicates for the EDM provider an unset/default value. 24 | */ 25 | private final int precision; 26 | 27 | /** 28 | * The scale of the type in use as Integer. 29 | * 30 | * A negative value indicates for the EDM provider an unset/default value. 31 | */ 32 | private final int scale; 33 | 34 | /** 35 | * true if unicode or null if not specified 36 | */ 37 | private final Boolean unicode; 38 | 39 | @Builder 40 | private EntityMetaPropertyPrimitve( 41 | boolean isKey, 42 | String name, 43 | String fieldName, 44 | Class fieldType, 45 | Boolean nullable, 46 | int maxLength, 47 | int precision, 48 | int scale, 49 | Boolean unicode) { 50 | 51 | super(isKey, name, fieldName, fieldType, nullable); 52 | this.maxLength = maxLength; 53 | this.precision = precision; 54 | this.scale = scale; 55 | this.unicode = unicode; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/bloggingit/odata/olingo/v4/factory/OlingoObjectFactory.java: -------------------------------------------------------------------------------- 1 | package com.bloggingit.odata.olingo.v4.factory; 2 | 3 | import com.bloggingit.odata.olingo.edm.meta.EntityMetaData; 4 | import com.bloggingit.odata.olingo.edm.meta.EntityMetaProperty; 5 | import com.bloggingit.odata.olingo.edm.meta.EntityMetaPropertyEntity; 6 | import com.bloggingit.odata.olingo.edm.meta.EntityMetaPropertyPrimitve; 7 | import com.bloggingit.odata.olingo.v4.mapper.FQNMapper; 8 | import java.net.URI; 9 | import java.net.URISyntaxException; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | import org.apache.commons.lang3.EnumUtils; 13 | import org.apache.olingo.commons.api.Constants; 14 | import org.apache.olingo.commons.api.data.ComplexValue; 15 | import org.apache.olingo.commons.api.data.Entity; 16 | import org.apache.olingo.commons.api.data.Link; 17 | import org.apache.olingo.commons.api.data.Property; 18 | import org.apache.olingo.commons.api.data.ValueType; 19 | import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind; 20 | import org.apache.olingo.commons.api.edm.FullQualifiedName; 21 | import org.apache.olingo.commons.api.edm.provider.CsdlComplexType; 22 | import org.apache.olingo.commons.api.edm.provider.CsdlEntityContainer; 23 | import org.apache.olingo.commons.api.edm.provider.CsdlEntityContainerInfo; 24 | import org.apache.olingo.commons.api.edm.provider.CsdlEntitySet; 25 | import org.apache.olingo.commons.api.edm.provider.CsdlEntityType; 26 | import org.apache.olingo.commons.api.edm.provider.CsdlEnumMember; 27 | import org.apache.olingo.commons.api.edm.provider.CsdlEnumType; 28 | import org.apache.olingo.commons.api.edm.provider.CsdlNavigationProperty; 29 | import org.apache.olingo.commons.api.edm.provider.CsdlNavigationPropertyBinding; 30 | import org.apache.olingo.commons.api.edm.provider.CsdlProperty; 31 | import org.apache.olingo.commons.api.edm.provider.CsdlPropertyRef; 32 | import org.apache.olingo.commons.api.edm.provider.CsdlSchema; 33 | import org.apache.olingo.commons.api.ex.ODataRuntimeException; 34 | 35 | /** 36 | * 37 | * @author mes 38 | */ 39 | public class OlingoObjectFactory { 40 | 41 | public static CsdlEntitySet createCsdlEntitySet(EntityMetaData meta, String serviceNamespace) { 42 | CsdlEntitySet csdlEntitySet = new CsdlEntitySet(); 43 | 44 | csdlEntitySet.setName(meta.getEntityTypeSetName()); 45 | 46 | csdlEntitySet.setType(FQNMapper.createFullQualifiedName(serviceNamespace, meta)); 47 | 48 | List navPropBindingList = new ArrayList<>(); 49 | for (EntityMetaProperty metaProp : meta.getProperties()) { 50 | if (metaProp instanceof EntityMetaPropertyEntity) { // add navigation ref only 51 | EntityMetaPropertyEntity metaPropEntity = (EntityMetaPropertyEntity) metaProp; 52 | navPropBindingList.add(createCsdlNavigationPropertyBinding(metaPropEntity.getEdmName(), metaPropEntity.getValueMetaData().getEntityTypeSetName())); 53 | } 54 | 55 | } 56 | csdlEntitySet.setNavigationPropertyBindings(navPropBindingList); 57 | 58 | 59 | return csdlEntitySet; 60 | } 61 | 62 | public static CsdlEntityContainer createCsdlEntityContainer(String containerName, List entitySets) { 63 | CsdlEntityContainer entityContainer = new CsdlEntityContainer(); 64 | 65 | entityContainer.setName(containerName); 66 | 67 | entityContainer.setEntitySets(entitySets); 68 | 69 | return entityContainer; 70 | } 71 | 72 | public static CsdlEntityContainerInfo createCsdlEntityContainerInfo(String serviceNamespace, String containerName) { 73 | CsdlEntityContainerInfo csdlEntityContainerInfo = new CsdlEntityContainerInfo(); 74 | 75 | FullQualifiedName containerNameFQ = FQNMapper.createFullQualifiedName(serviceNamespace, containerName); 76 | 77 | csdlEntityContainerInfo.setContainerName(containerNameFQ); 78 | 79 | return csdlEntityContainerInfo; 80 | } 81 | 82 | public static CsdlSchema createCsdlSchema(String serviceNamespace, CsdlEntityContainer entityContainer, List entityTypes, List enumTypes, List complexTypes) { 83 | CsdlSchema csdlSchema = new CsdlSchema(); 84 | 85 | csdlSchema.setNamespace(serviceNamespace); 86 | csdlSchema.setEntityContainer(entityContainer); 87 | csdlSchema.setEntityTypes(entityTypes); 88 | csdlSchema.setEnumTypes(enumTypes); 89 | csdlSchema.setComplexTypes(complexTypes); 90 | 91 | return csdlSchema; 92 | } 93 | 94 | public static CsdlProperty createCsdlProperty(EntityMetaProperty metaProp, FullQualifiedName typeFQN, String defaultVal) { 95 | CsdlProperty csdlProperty = new CsdlProperty(); 96 | 97 | csdlProperty.setName(metaProp.getEdmName()).setType(typeFQN); 98 | 99 | if (metaProp instanceof EntityMetaPropertyPrimitve) { 100 | 101 | EntityMetaPropertyPrimitve primitiveProp = (EntityMetaPropertyPrimitve) metaProp; 102 | 103 | csdlProperty 104 | .setNullable(metaProp.getNullable()); 105 | 106 | if (primitiveProp.getMaxLength() != -1) { 107 | csdlProperty.setMaxLength(primitiveProp.getMaxLength()); 108 | } 109 | if (primitiveProp.getPrecision() != -1) { 110 | csdlProperty.setPrecision(primitiveProp.getPrecision()); 111 | } 112 | if (primitiveProp.getScale() != -1) { 113 | csdlProperty.setScale(primitiveProp.getScale()); 114 | } 115 | if (defaultVal != null) { 116 | csdlProperty.setDefaultValue(defaultVal); 117 | } 118 | } 119 | 120 | return csdlProperty; 121 | } 122 | /** 123 | * 124 | * @param path target entitySet, where the nav prop points to 125 | * @param target target entitySet, where the nav prop points to 126 | * @return a CsdlNavigationPropertyBinding for the entity set 127 | */ 128 | public static CsdlNavigationPropertyBinding createCsdlNavigationPropertyBinding(String path, String target) { 129 | CsdlNavigationPropertyBinding csdlNavigationPropertyBinding = new CsdlNavigationPropertyBinding(); 130 | csdlNavigationPropertyBinding.setPath(path); 131 | csdlNavigationPropertyBinding.setTarget(target); 132 | return csdlNavigationPropertyBinding; 133 | } 134 | 135 | public static > CsdlEnumType createEnumType(final Class enumClass) { 136 | 137 | List enumList = EnumUtils.getEnumList(enumClass); 138 | 139 | List csdlEnumMember = new ArrayList<>(); 140 | 141 | enumList.forEach((entry) -> { 142 | csdlEnumMember.add(new CsdlEnumMember().setName(entry.name()).setValue(String.valueOf(entry.ordinal()))); 143 | }); 144 | 145 | return new CsdlEnumType() 146 | .setName(enumClass.getSimpleName()) 147 | .setMembers(csdlEnumMember) 148 | .setUnderlyingType(EdmPrimitiveTypeKind.Int32.getFullQualifiedName()); 149 | } 150 | 151 | public static CsdlEntityType createCsdlEntityType(String entityTypeName, List properties, List csdlNavigationProperty, List keys) { 152 | CsdlEntityType csdlEntityType = new CsdlEntityType(); 153 | 154 | csdlEntityType 155 | .setName(entityTypeName) 156 | .setProperties(properties) 157 | .setNavigationProperties(csdlNavigationProperty) 158 | .setKey(keys); 159 | 160 | return csdlEntityType; 161 | } 162 | 163 | public static CsdlComplexType createCsdlComplexType(String entityTypeName, List properties) { 164 | CsdlComplexType csdlComplexType = new CsdlComplexType(); 165 | 166 | csdlComplexType 167 | .setName(entityTypeName) 168 | .setProperties(properties); 169 | 170 | return csdlComplexType; 171 | } 172 | 173 | public static CsdlPropertyRef createCsdlPropertyRef(String name) { 174 | CsdlPropertyRef csdlPropertyRef = new CsdlPropertyRef(); 175 | 176 | csdlPropertyRef.setName(name); 177 | 178 | return csdlPropertyRef; 179 | } 180 | 181 | // https://olingo.apache.org/doc/odata4/tutorials/navigation/tutorial_navigation.html 182 | /** 183 | * 184 | * @param typeFQ fully qualified name of the entity type to which we’re 185 | * navigating. 186 | * @param name the name of the navigation property is used as segment in the 187 | * URI. 188 | * @param nullable if the navigation target is required. the default is 189 | * assumed to be “true”. 190 | * @param partner An attribute, used to define a bi-directional 191 | * relationship. Specifies a path from the entity type to the navigation 192 | * property. In our example, we can navigate from book to author and from 193 | * author to book 194 | * @return a navigation property 195 | */ 196 | public static CsdlNavigationProperty createCsdlNavigationProperty(FullQualifiedName typeFQ, String name, Boolean nullable, String partner) { 197 | boolean isNullable = (nullable == null) ? true : nullable; 198 | return new CsdlNavigationProperty() 199 | .setName(name) 200 | .setType(typeFQ) 201 | .setNullable(isNullable) 202 | .setPartner(partner); 203 | } 204 | 205 | 206 | public static ComplexValue createComplexValue(List properties) { 207 | ComplexValue complexValue = new ComplexValue(); 208 | List complexSubValues = complexValue.getValue(); 209 | complexSubValues.addAll(properties); 210 | 211 | return complexValue; 212 | } 213 | 214 | public static Property createProperty(String name, ValueType valueType, Object value) { 215 | return new Property(null, name, valueType, value); 216 | } 217 | 218 | public static Entity createEntity(List properties, List navigationLinks, URI key) { 219 | Entity entity = new Entity(); 220 | entity.getProperties().addAll(properties); 221 | entity.getNavigationLinks().addAll(navigationLinks); 222 | entity.setId(key); 223 | 224 | return entity; 225 | } 226 | 227 | public static Link createLink(String title, Entity entity) { 228 | Link link = new Link(); 229 | link.setTitle(title); 230 | link.setInlineEntity(entity); 231 | link.setType(Constants.ENTITY_NAVIGATION_LINK_TYPE); 232 | link.setRel(Constants.NS_ASSOCIATION_LINK_REL + title); 233 | return link; 234 | } 235 | 236 | public static URI createId(String entitySetName, Object id) { 237 | try { 238 | return new URI(entitySetName + "(" + String.valueOf(id) + ")"); 239 | } catch (URISyntaxException e) { 240 | throw new ODataRuntimeException("Unable to create id for entity: " + entitySetName, e); 241 | } 242 | } 243 | 244 | public static T createInstance(final Class clazz) { 245 | try { 246 | return clazz.newInstance(); 247 | } catch (InstantiationException | IllegalAccessException ex) { 248 | throw new ODataRuntimeException(ex); 249 | } 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /src/main/java/com/bloggingit/odata/olingo/v4/mapper/FQNMapper.java: -------------------------------------------------------------------------------- 1 | package com.bloggingit.odata.olingo.v4.mapper; 2 | 3 | import com.bloggingit.odata.olingo.edm.meta.EntityMetaData; 4 | import com.bloggingit.odata.olingo.edm.meta.EntityMetaDataContainer; 5 | import com.bloggingit.odata.olingo.edm.meta.EntityMetaProperty; 6 | import com.bloggingit.odata.olingo.edm.meta.EntityMetaPropertyComplex; 7 | import com.bloggingit.odata.olingo.edm.meta.EntityMetaPropertyEntity; 8 | import com.bloggingit.odata.olingo.edm.meta.EntityMetaPropertyEnum; 9 | import com.bloggingit.odata.olingo.edm.meta.EntityMetaPropertyPrimitve; 10 | import org.apache.olingo.commons.api.edm.FullQualifiedName; 11 | 12 | /** 13 | * 14 | * @author mes 15 | */ 16 | public class FQNMapper { 17 | 18 | public static FullQualifiedName createFullQualifiedName(String serviceNamespace, String name) { 19 | return new FullQualifiedName(serviceNamespace, name); 20 | } 21 | 22 | public static FullQualifiedName createFullQualifiedName(EntityMetaDataContainer entityMetaDataContainer) { 23 | return createFullQualifiedName(entityMetaDataContainer.getServiceNamespace(), entityMetaDataContainer.getEdmContainerName()); 24 | } 25 | 26 | public static FullQualifiedName createFullQualifiedName(String serviceNamespace, EntityMetaData meta) { 27 | return createFullQualifiedName(serviceNamespace, meta.getEntityTypeName()); 28 | } 29 | 30 | 31 | public static FullQualifiedName mapToPropertyValueTypeFQN(String serviceNamespace, EntityMetaProperty metaProp) { 32 | FullQualifiedName typeFQN = null; 33 | if (metaProp instanceof EntityMetaPropertyEnum || metaProp instanceof EntityMetaPropertyComplex || metaProp instanceof EntityMetaPropertyEntity) { 34 | typeFQN = createFullQualifiedName(serviceNamespace, metaProp.getFieldType().getSimpleName()); 35 | } else if (metaProp instanceof EntityMetaPropertyPrimitve) { 36 | typeFQN = OlingoTypeMapper.mapToEdmPrimitiveTypeKind(metaProp.getFieldType()).getFullQualifiedName(); 37 | } 38 | return typeFQN; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/bloggingit/odata/olingo/v4/mapper/OlingoObjectMapper.java: -------------------------------------------------------------------------------- 1 | package com.bloggingit.odata.olingo.v4.mapper; 2 | 3 | import com.bloggingit.odata.olingo.edm.meta.EntityMetaData; 4 | import com.bloggingit.odata.olingo.edm.meta.EntityMetaProperty; 5 | import com.bloggingit.odata.olingo.edm.meta.EntityMetaPropertyComplex; 6 | import com.bloggingit.odata.olingo.edm.meta.EntityMetaPropertyEntity; 7 | import com.bloggingit.odata.olingo.v4.factory.OlingoObjectFactory; 8 | import com.bloggingit.odata.olingo.v4.util.ReflectionUtils; 9 | import java.net.URI; 10 | import java.util.ArrayList; 11 | import java.util.Calendar; 12 | import java.util.Collection; 13 | import java.util.List; 14 | import org.apache.commons.lang3.EnumUtils; 15 | import org.apache.olingo.commons.api.data.ComplexValue; 16 | import org.apache.olingo.commons.api.data.Entity; 17 | import org.apache.olingo.commons.api.data.EntityCollection; 18 | import org.apache.olingo.commons.api.data.Link; 19 | import org.apache.olingo.commons.api.data.Linked; 20 | import org.apache.olingo.commons.api.data.Property; 21 | import org.apache.olingo.commons.api.data.ValueType; 22 | import org.apache.olingo.commons.api.edm.FullQualifiedName; 23 | import org.apache.olingo.commons.api.edm.provider.CsdlEnumType; 24 | import org.apache.olingo.commons.api.edm.provider.CsdlNavigationProperty; 25 | import org.apache.olingo.commons.api.edm.provider.CsdlProperty; 26 | import org.apache.olingo.commons.api.edm.provider.CsdlPropertyRef; 27 | import org.apache.olingo.commons.api.edm.provider.CsdlStructuralType; 28 | 29 | /** 30 | * Mapper provides methods to convert data into a certain Apache Olingo data 31 | * structure. 32 | */ 33 | public class OlingoObjectMapper { 34 | 35 | public static EntityCollection mapObjectEntitiesToOlingoEntityCollection(Collection objEntities, EntityMetaData entityMetaData) { 36 | EntityCollection entityCollection = new EntityCollection(); 37 | entityCollection.setCount(objEntities.size()); 38 | 39 | objEntities.forEach((entity) -> { 40 | entityCollection 41 | .getEntities() 42 | .add(mapObjEntityToOlingoEntity(entityMetaData, entity)); 43 | }); 44 | return entityCollection; 45 | } 46 | 47 | public static Entity mapObjEntityToOlingoEntity(EntityMetaData entityMetaData, Object objEntity) { 48 | 49 | List metaProperties = entityMetaData.getProperties(); 50 | 51 | List properties = new ArrayList<>(); 52 | List navigationLinks = new ArrayList<>(); 53 | 54 | URI keyId = null; 55 | for (EntityMetaProperty metaProp : metaProperties) { 56 | if (metaProp instanceof EntityMetaPropertyEntity) { 57 | Object metaPropValue = ReflectionUtils.invokePropertyGetter(metaProp.getFieldName(), objEntity); 58 | if (metaPropValue != null) { 59 | EntityMetaData valueMetaData = ((EntityMetaPropertyEntity) metaProp).getValueMetaData(); 60 | Entity subEntity = mapObjEntityToOlingoEntity(valueMetaData, metaPropValue); 61 | navigationLinks.add(OlingoObjectFactory.createLink(metaProp.getEdmName(), subEntity)); 62 | } 63 | } else { 64 | Property property = mapMetaPropertyDataToOlingoProperty(objEntity, metaProp); 65 | properties.add(property); 66 | 67 | if (metaProp.isKey()) { 68 | keyId = OlingoObjectFactory.createId(entityMetaData.getEntityTypeSetName(), property.getValue()); 69 | } 70 | } 71 | } 72 | 73 | return OlingoObjectFactory.createEntity(properties, navigationLinks, keyId); 74 | } 75 | 76 | public static T mapOlingoEntityToObjectEntity(EntityMetaData entityMetaData, Linked linkedEntity) { 77 | 78 | T objEntity = OlingoObjectFactory.createInstance(entityMetaData.getEntityClass()); 79 | 80 | List metaProperties = entityMetaData.getProperties(); 81 | 82 | metaProperties.forEach((metaProp) -> { 83 | Object val = null; 84 | if (metaProp instanceof EntityMetaPropertyEntity) { 85 | EntityMetaData propEntityMetaData = ((EntityMetaPropertyEntity) metaProp).getValueMetaData(); 86 | Link navigationLink = linkedEntity.getNavigationLink(metaProp.getEdmName()); 87 | Entity propInlineEntity = navigationLink.getInlineEntity(); 88 | val = mapOlingoEntityToObjectEntity(propEntityMetaData, propInlineEntity); 89 | } else if (metaProp instanceof EntityMetaPropertyComplex) { 90 | EntityMetaData propComplexMetaData = ((EntityMetaPropertyComplex) metaProp).getValueMetaData(); 91 | Property property = ((Entity) linkedEntity).getProperty(metaProp.getEdmName()); 92 | if (property != null) { 93 | val = mapOlingoEntityToObjectEntity(propComplexMetaData, (ComplexValue) property.getValue()); 94 | } 95 | } else if (linkedEntity instanceof Entity) { 96 | Property property = ((Entity) linkedEntity).getProperty(metaProp.getEdmName()); 97 | val = mapOlingoPropertyToObjValue(property, metaProp); 98 | } else if (linkedEntity instanceof ComplexValue) { 99 | for (Property property : ((ComplexValue) linkedEntity).getValue()) { 100 | if (metaProp.getEdmName().equals(property.getName())) { 101 | val = mapOlingoPropertyToObjValue(property, metaProp); 102 | break; 103 | } 104 | } 105 | } 106 | if (val != null) { 107 | ReflectionUtils.invokePropertySetter(metaProp.getFieldName(), objEntity, val); 108 | } 109 | }); 110 | 111 | return objEntity; 112 | } 113 | 114 | @SuppressWarnings("unchecked") 115 | public static > List mapEntityMetaDataToCsdlEnumTypeList(final EntityMetaData meta) { 116 | List enumTypes = new ArrayList<>(); 117 | 118 | meta.getEnumPropertyData().forEach((prop) -> { 119 | enumTypes.add(OlingoObjectFactory.createEnumType((Class) prop.getFieldType())); 120 | }); 121 | 122 | return enumTypes; 123 | } 124 | 125 | public static CsdlStructuralType mapEntityMetaDataToCsdlStructuralType(EntityMetaData metaData, String serviceNamespace) { 126 | Class objEntityClass = metaData.getEntityClass(); 127 | 128 | List properties = new ArrayList<>(); 129 | List keys = new ArrayList<>(); 130 | List navigations = new ArrayList<>(); 131 | 132 | metaData.getProperties().stream().map((metaProp) -> { 133 | 134 | FullQualifiedName typeFQN = FQNMapper.mapToPropertyValueTypeFQN(serviceNamespace, metaProp); 135 | Object objEntity = OlingoObjectFactory.createInstance(objEntityClass); 136 | 137 | if (metaProp instanceof EntityMetaPropertyEntity) { // add navigation ref only 138 | EntityMetaPropertyEntity metaPropEntity = (EntityMetaPropertyEntity) metaProp; 139 | navigations.add(OlingoObjectFactory.createCsdlNavigationProperty(typeFQN, metaPropEntity.getEdmName(), metaPropEntity.getNullable(), null)); 140 | } else { 141 | Object val = ReflectionUtils.invokePropertyGetter(metaProp.getFieldName(), objEntity); 142 | properties.add(OlingoObjectFactory.createCsdlProperty(metaProp, typeFQN, String.valueOf(val))); 143 | } 144 | 145 | return metaProp; 146 | }).filter((metaProp) -> (metaProp.isKey())).forEachOrdered((metaProp) -> { 147 | keys.add(OlingoObjectFactory.createCsdlPropertyRef(metaProp.getEdmName())); 148 | }); 149 | 150 | CsdlStructuralType structuralType; 151 | if (metaData.isComplexType()) { 152 | structuralType = OlingoObjectFactory.createCsdlComplexType(metaData.getEntityTypeName(), properties); 153 | } else { 154 | structuralType = OlingoObjectFactory.createCsdlEntityType(metaData.getEntityTypeName(), properties, navigations, keys); 155 | } 156 | 157 | return structuralType; 158 | } 159 | 160 | @SuppressWarnings("unchecked") 161 | public static > Object mapOlingoPropertyToObjValue(Property property, EntityMetaProperty metaProp) { 162 | 163 | Class fieldType = metaProp.getFieldType(); 164 | 165 | Object val = null; 166 | if (property != null) { 167 | val = property.getValue(); 168 | if (val != null && !fieldType.equals(val.getClass())) { 169 | if (property.isEnum()) { 170 | val = EnumUtils.getEnumList((Class) fieldType).get(Integer.parseInt(val.toString())); 171 | } else if (val instanceof Calendar) { 172 | val = ((Calendar) val).getTime(); 173 | } 174 | } 175 | } 176 | return val; 177 | } 178 | 179 | @SuppressWarnings("unchecked") 180 | public static > Property mapMetaPropertyDataToOlingoProperty(T objEntity, EntityMetaProperty metaProp) { 181 | Object val = ReflectionUtils.invokePropertyGetter(metaProp.getFieldName(), objEntity); 182 | ValueType valueType = OlingoTypeMapper.mapToValueType(metaProp); 183 | Object oDataValue = null; 184 | if (val != null) { 185 | if (null == valueType) { //primitive fallback 186 | oDataValue = val; 187 | } else { 188 | switch (valueType) { 189 | case ENUM: 190 | oDataValue = EnumUtils.getEnum((Class) metaProp.getFieldType(), String.valueOf(val)).ordinal(); 191 | break; 192 | case COMPLEX: 193 | oDataValue = mapMetaPropertyValueToOlingoLinked(val, metaProp); 194 | break; 195 | case ENTITY: 196 | throw new IllegalArgumentException("Can't create entity property. Use navigation ref instead"); 197 | default: 198 | //primitive fallback 199 | oDataValue = val; 200 | break; 201 | } 202 | } 203 | } 204 | 205 | return OlingoObjectFactory.createProperty(metaProp.getEdmName(), valueType, oDataValue); 206 | } 207 | 208 | @SuppressWarnings("unchecked") 209 | public static > Linked mapMetaPropertyValueToOlingoLinked(Object value, EntityMetaProperty metaProp) { 210 | Linked valuable = null; 211 | 212 | if (value != null && metaProp instanceof EntityMetaPropertyComplex) { 213 | EntityMetaPropertyComplex propComplex = (EntityMetaPropertyComplex) metaProp; 214 | 215 | List subProperties = new ArrayList<>(); 216 | propComplex.getValueMetaData().getProperties().forEach((subMetaProp) -> { 217 | subProperties.add(mapMetaPropertyDataToOlingoProperty(value, subMetaProp)); 218 | }); 219 | 220 | valuable = OlingoObjectFactory.createComplexValue(subProperties); 221 | } 222 | return valuable; 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /src/main/java/com/bloggingit/odata/olingo/v4/mapper/OlingoTypeMapper.java: -------------------------------------------------------------------------------- 1 | package com.bloggingit.odata.olingo.v4.mapper; 2 | 3 | import com.bloggingit.odata.olingo.edm.meta.EntityMetaProperty; 4 | import com.bloggingit.odata.olingo.edm.meta.EntityMetaPropertyComplex; 5 | import com.bloggingit.odata.olingo.edm.meta.EntityMetaPropertyEntity; 6 | import com.bloggingit.odata.olingo.edm.meta.EntityMetaPropertyEnum; 7 | import com.bloggingit.odata.olingo.edm.meta.EntityMetaPropertyPrimitve; 8 | import java.math.BigDecimal; 9 | import java.math.BigInteger; 10 | import java.util.Arrays; 11 | import java.util.Calendar; 12 | import java.util.Date; 13 | import java.util.HashMap; 14 | import java.util.List; 15 | import java.util.Map; 16 | import java.util.UUID; 17 | import org.apache.olingo.commons.api.data.ValueType; 18 | import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind; 19 | 20 | public class OlingoTypeMapper { 21 | 22 | /** 23 | * Maps classes to their corresponding ed, primitive {@code Class}es 24 | */ 25 | public final static Map>, EdmPrimitiveTypeKind> CLASS_TO_TYPE_KIND_MAP = new HashMap<>(); 26 | 27 | static { 28 | CLASS_TO_TYPE_KIND_MAP.put(Arrays.asList(String.class), EdmPrimitiveTypeKind.String); 29 | CLASS_TO_TYPE_KIND_MAP.put(Arrays.asList(boolean.class, Boolean.class), EdmPrimitiveTypeKind.Boolean); 30 | CLASS_TO_TYPE_KIND_MAP.put(Arrays.asList(byte.class, Byte.class), EdmPrimitiveTypeKind.SByte); 31 | CLASS_TO_TYPE_KIND_MAP.put(Arrays.asList(byte[].class, Byte[].class), EdmPrimitiveTypeKind.Binary); 32 | CLASS_TO_TYPE_KIND_MAP.put(Arrays.asList(short.class, Short.class), EdmPrimitiveTypeKind.Int16); 33 | CLASS_TO_TYPE_KIND_MAP.put(Arrays.asList(int.class, Integer.class), EdmPrimitiveTypeKind.Int32); 34 | CLASS_TO_TYPE_KIND_MAP.put(Arrays.asList(long.class, Long.class), EdmPrimitiveTypeKind.Int64); 35 | CLASS_TO_TYPE_KIND_MAP.put(Arrays.asList(float.class, Float.class), EdmPrimitiveTypeKind.Single); 36 | CLASS_TO_TYPE_KIND_MAP.put(Arrays.asList(double.class, Double.class), EdmPrimitiveTypeKind.Double); 37 | CLASS_TO_TYPE_KIND_MAP.put(Arrays.asList(BigInteger.class, BigDecimal.class), EdmPrimitiveTypeKind.Decimal); 38 | CLASS_TO_TYPE_KIND_MAP.put(Arrays.asList(Date.class), EdmPrimitiveTypeKind.Date); 39 | CLASS_TO_TYPE_KIND_MAP.put(Arrays.asList(Calendar.class), EdmPrimitiveTypeKind.DateTimeOffset); 40 | CLASS_TO_TYPE_KIND_MAP.put(Arrays.asList(UUID.class), EdmPrimitiveTypeKind.Guid); 41 | 42 | } 43 | 44 | public static ValueType mapToValueType(EntityMetaProperty metaProp) { 45 | ValueType valType = null; 46 | 47 | if (metaProp instanceof EntityMetaPropertyPrimitve) { 48 | valType = ValueType.PRIMITIVE; 49 | } else if (metaProp instanceof EntityMetaPropertyEnum) { 50 | valType = ValueType.ENUM; 51 | } else if (metaProp instanceof EntityMetaPropertyComplex) { 52 | valType = ValueType.COMPLEX; 53 | } else if (metaProp instanceof EntityMetaPropertyEntity) { 54 | valType = ValueType.ENTITY; 55 | } 56 | 57 | return valType; 58 | } 59 | 60 | public static EdmPrimitiveTypeKind mapToEdmPrimitiveTypeKind(Class fieldType) { 61 | EdmPrimitiveTypeKind converted = null; 62 | 63 | for (Map.Entry>, EdmPrimitiveTypeKind> entry : CLASS_TO_TYPE_KIND_MAP.entrySet()) { 64 | if (entry.getKey().contains(fieldType)) { 65 | converted = entry.getValue(); 66 | break; 67 | } 68 | } 69 | 70 | if (converted == null) { 71 | throw new UnsupportedOperationException("Not a supported type '" + fieldType + "'."); 72 | } 73 | 74 | return converted; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/com/bloggingit/odata/olingo/v4/processor/AbstractEntityMetaDataProcessor.java: -------------------------------------------------------------------------------- 1 | package com.bloggingit.odata.olingo.v4.processor; 2 | 3 | import java.io.InputStream; 4 | import java.util.List; 5 | import java.util.Locale; 6 | import org.apache.olingo.commons.api.edm.EdmEntitySet; 7 | import org.apache.olingo.commons.api.edm.EdmProperty; 8 | import org.apache.olingo.commons.api.format.ContentType; 9 | import org.apache.olingo.commons.api.http.HttpHeader; 10 | import org.apache.olingo.commons.api.http.HttpStatusCode; 11 | import org.apache.olingo.server.api.ODataApplicationException; 12 | import org.apache.olingo.server.api.ODataResponse; 13 | import org.apache.olingo.server.api.uri.UriInfoResource; 14 | import org.apache.olingo.server.api.uri.UriParameter; 15 | import org.apache.olingo.server.api.uri.UriResource; 16 | import org.apache.olingo.server.api.uri.UriResourceEntitySet; 17 | import org.apache.olingo.server.api.uri.UriResourceProperty; 18 | 19 | /** 20 | * 21 | * @author mes 22 | */ 23 | public abstract class AbstractEntityMetaDataProcessor { 24 | 25 | protected void setResponseContentAndOkStatus(ODataResponse response, InputStream content, ContentType responseFormat) { 26 | response.setContent(content); 27 | response.setStatusCode(HttpStatusCode.OK.getStatusCode()); 28 | response.setHeader(HttpHeader.CONTENT_TYPE, responseFormat.toContentTypeString()); 29 | } 30 | 31 | protected void setResponseNoContentStatus(ODataResponse response) { 32 | response.setStatusCode(HttpStatusCode.NO_CONTENT.getStatusCode()); 33 | } 34 | 35 | protected EdmEntitySet getUriResourceEdmEntitySet(UriInfoResource uriInfo) throws ODataApplicationException { 36 | return getUriResourceEntitySet(uriInfo).getEntitySet(); 37 | } 38 | 39 | protected List getUriResourceKeyPredicates(UriInfoResource uriInfo) throws ODataApplicationException { 40 | return getUriResourceEntitySet(uriInfo).getKeyPredicates(); 41 | } 42 | 43 | protected UriResourceEntitySet getUriResourceEntitySet(UriInfoResource uriInfo) throws ODataApplicationException { 44 | List resourceParts = uriInfo.getUriResourceParts(); 45 | // To get the entity set we have to interpret all URI segments 46 | if (!(resourceParts.get(0) instanceof UriResourceEntitySet)) { 47 | // Here we should interpret the whole URI but in this example we do not support navigation so we throw an exception 48 | throw new ODataApplicationException("Invalid resource type for first segment.", HttpStatusCode.NOT_IMPLEMENTED 49 | .getStatusCode(), Locale.ENGLISH); 50 | } 51 | 52 | return (UriResourceEntitySet) resourceParts.get(0); 53 | } 54 | 55 | protected EdmProperty getUriResourceEdmProperty(UriInfoResource uriInfo) throws ODataApplicationException { 56 | 57 | List resourceParts = uriInfo.getUriResourceParts(); 58 | // the last segment is the Property 59 | int idx = resourceParts.size() - 1; 60 | 61 | if (!(resourceParts.get(idx) instanceof UriResourceProperty)) { 62 | // Here we should interpret the whole URI but in this example we do not support navigation so we throw an exception 63 | throw new ODataApplicationException("Invalid resource type for last segment.", HttpStatusCode.NOT_IMPLEMENTED 64 | .getStatusCode(), Locale.ENGLISH); 65 | } 66 | 67 | UriResourceProperty uriProperty = (UriResourceProperty) resourceParts.get(idx); 68 | return uriProperty.getProperty(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/bloggingit/odata/olingo/v4/processor/DataCollectionProcessor.java: -------------------------------------------------------------------------------- 1 | package com.bloggingit.odata.olingo.v4.processor; 2 | 3 | import com.bloggingit.odata.olingo.edm.meta.EntityMetaData; 4 | import com.bloggingit.odata.olingo.edm.meta.EntityMetaDataContainer; 5 | import com.bloggingit.odata.olingo.v4.service.OlingoDataService; 6 | import java.io.ByteArrayInputStream; 7 | import java.nio.charset.Charset; 8 | 9 | import org.apache.olingo.commons.api.data.ContextURL; 10 | import org.apache.olingo.commons.api.data.EntityCollection; 11 | import org.apache.olingo.commons.api.edm.EdmEntitySet; 12 | import org.apache.olingo.commons.api.edm.EdmEntityType; 13 | import org.apache.olingo.commons.api.format.ContentType; 14 | import org.apache.olingo.server.api.OData; 15 | import org.apache.olingo.server.api.ODataApplicationException; 16 | import org.apache.olingo.server.api.ODataLibraryException; 17 | import org.apache.olingo.server.api.ODataRequest; 18 | import org.apache.olingo.server.api.ODataResponse; 19 | import org.apache.olingo.server.api.ServiceMetadata; 20 | import org.apache.olingo.server.api.processor.CountEntityCollectionProcessor; 21 | import org.apache.olingo.server.api.serializer.EntityCollectionSerializerOptions; 22 | import org.apache.olingo.server.api.serializer.ODataSerializer; 23 | import org.apache.olingo.server.api.serializer.SerializerException; 24 | import org.apache.olingo.server.api.serializer.SerializerResult; 25 | import org.apache.olingo.server.api.uri.UriInfo; 26 | import org.apache.olingo.server.api.uri.queryoption.CountOption; 27 | 28 | /** 29 | * This class is invoked by the Apache Olingo framework when the the OData 30 | * service is invoked order to display a list/collection of data (entities). 31 | * 32 | * This is the case if an EntitySet is requested by the user. 33 | */ 34 | public class DataCollectionProcessor extends AbstractEntityMetaDataProcessor implements CountEntityCollectionProcessor { 35 | 36 | private OData odata; 37 | private ServiceMetadata serviceMetadata; 38 | private final EntityMetaDataContainer entityMetaDataCollection; 39 | 40 | private final OlingoDataService dataService; 41 | 42 | public DataCollectionProcessor(OlingoDataService dataService, EntityMetaDataContainer entityMetaDataCollection) { 43 | this.dataService = dataService; 44 | this.entityMetaDataCollection = entityMetaDataCollection; 45 | } 46 | 47 | @Override 48 | public void init(OData odata, ServiceMetadata serviceMetadata) { 49 | this.odata = odata; 50 | this.serviceMetadata = serviceMetadata; 51 | } 52 | 53 | @Override 54 | public void readEntityCollection(ODataRequest request, ODataResponse response, UriInfo uriInfo, ContentType responseFormat) throws ODataApplicationException, SerializerException { 55 | 56 | // 1st we have retrieve the requested EntitySet from the uriInfo object (representation of the parsed service URI) 57 | EdmEntitySet edmEntitySet = getUriResourceEdmEntitySet(uriInfo); 58 | 59 | // 2nd: fetch the data from backend for this requested EntitySetName 60 | // it has to be delivered as EntitySet object 61 | EntityMetaData meta = this.entityMetaDataCollection.getEntityMetaDataByTypeSetName(edmEntitySet.getName()); 62 | 63 | EntityCollection entitySet = dataService.getEntityDataList(meta); 64 | 65 | //The $count system query option with a value of true specifies that the total count 66 | //of items within a collection matching the request be returned along with the result. 67 | CountOption countOption = uriInfo.getCountOption(); 68 | if (countOption != null) { 69 | boolean isCount = countOption.getValue(); 70 | if (isCount) { 71 | entitySet.setCount(entitySet.getEntities().size()); 72 | } 73 | } 74 | 75 | 76 | // 3rd: create a serializer based on the requested format (json) 77 | ODataSerializer serializer = odata.createSerializer(responseFormat); 78 | 79 | // 4th: Now serialize the content: transform from the EntitySet object to InputStream 80 | EdmEntityType edmEntityType = edmEntitySet.getEntityType(); 81 | ContextURL contextUrl = ContextURL.with().entitySet(edmEntitySet).build(); 82 | 83 | final String id = request.getRawBaseUri() + "/" + edmEntitySet.getName(); 84 | EntityCollectionSerializerOptions opts 85 | = EntityCollectionSerializerOptions 86 | .with() 87 | .id(id) 88 | .count(countOption) 89 | .contextURL(contextUrl) 90 | .build(); 91 | SerializerResult serializedContent = serializer.entityCollection(serviceMetadata, edmEntityType, entitySet, opts); 92 | 93 | // Finally: configure the response object: set the body, headers and status code 94 | setResponseContentAndOkStatus(response, serializedContent.getContent(), responseFormat); 95 | } 96 | 97 | @Override 98 | public void countEntityCollection(ODataRequest request, ODataResponse response, UriInfo uriInfo) throws ODataApplicationException, ODataLibraryException { 99 | 100 | // 1st we have retrieve the requested EntitySet from the uriInfo object (representation of the parsed service URI) 101 | EdmEntitySet edmEntitySet = getUriResourceEdmEntitySet(uriInfo); 102 | 103 | // 2nd: fetch the data from backend for this requested EntitySetName 104 | // it has to be delivered as EntitySet object 105 | EntityMetaData meta = this.entityMetaDataCollection.getEntityMetaDataByTypeSetName(edmEntitySet.getName()); 106 | 107 | EntityCollection entitySet = dataService.getEntityDataList(meta); 108 | 109 | // 3. serialize 110 | Integer entityCount = entitySet.getCount(); 111 | if (entityCount != null) { 112 | String valueStr = String.valueOf(entityCount); 113 | ByteArrayInputStream serializerContent = new ByteArrayInputStream( 114 | valueStr.getBytes(Charset.forName("UTF-8"))); 115 | 116 | // configure the response object 117 | setResponseContentAndOkStatus(response, serializerContent, ContentType.TEXT_PLAIN); 118 | } else { 119 | // in case there's no value for the property, we can skip the serialization 120 | setResponseNoContentStatus(response); 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/main/java/com/bloggingit/odata/olingo/v4/processor/DataEntityProcessor.java: -------------------------------------------------------------------------------- 1 | package com.bloggingit.odata.olingo.v4.processor; 2 | 3 | import com.bloggingit.odata.olingo.edm.meta.EntityMetaData; 4 | import com.bloggingit.odata.olingo.edm.meta.EntityMetaDataContainer; 5 | import com.bloggingit.odata.olingo.v4.service.OlingoDataService; 6 | import com.bloggingit.odata.olingo.v4.util.UriInfoUtil; 7 | import java.io.InputStream; 8 | import java.util.List; 9 | import java.util.Locale; 10 | import org.apache.olingo.commons.api.data.ContextURL; 11 | import org.apache.olingo.commons.api.data.Entity; 12 | import org.apache.olingo.commons.api.edm.EdmEntitySet; 13 | import org.apache.olingo.commons.api.edm.EdmEntityType; 14 | import org.apache.olingo.commons.api.edm.EdmNavigationProperty; 15 | import org.apache.olingo.commons.api.format.ContentType; 16 | import org.apache.olingo.commons.api.http.HttpMethod; 17 | import org.apache.olingo.commons.api.http.HttpStatusCode; 18 | import org.apache.olingo.server.api.OData; 19 | import org.apache.olingo.server.api.ODataApplicationException; 20 | import org.apache.olingo.server.api.ODataLibraryException; 21 | import org.apache.olingo.server.api.ODataRequest; 22 | import org.apache.olingo.server.api.ODataResponse; 23 | import org.apache.olingo.server.api.ServiceMetadata; 24 | import org.apache.olingo.server.api.deserializer.DeserializerResult; 25 | import org.apache.olingo.server.api.deserializer.ODataDeserializer; 26 | import org.apache.olingo.server.api.processor.EntityProcessor; 27 | import org.apache.olingo.server.api.serializer.EntitySerializerOptions; 28 | import org.apache.olingo.server.api.serializer.ODataSerializer; 29 | import org.apache.olingo.server.api.serializer.SerializerResult; 30 | import org.apache.olingo.server.api.uri.UriInfo; 31 | import org.apache.olingo.server.api.uri.UriParameter; 32 | import org.apache.olingo.server.api.uri.UriResource; 33 | import org.apache.olingo.server.api.uri.UriResourceEntitySet; 34 | import org.apache.olingo.server.api.uri.UriResourceNavigation; 35 | 36 | /** 37 | * This class is invoked by the Apache Olingo framework when the the OData 38 | * service is invoked order to display the data of a entity. 39 | * 40 | * This is the case if an Entity is requested by the user. 41 | */ 42 | public class DataEntityProcessor extends AbstractEntityMetaDataProcessor implements EntityProcessor { 43 | 44 | private OData odata; 45 | private ServiceMetadata serviceMetadata; 46 | private final EntityMetaDataContainer entityMetaDataCollection; 47 | 48 | private final OlingoDataService dataService; 49 | 50 | public DataEntityProcessor(OlingoDataService dataService, EntityMetaDataContainer entityMetaDataCollection) { 51 | this.dataService = dataService; 52 | this.entityMetaDataCollection = entityMetaDataCollection; 53 | } 54 | 55 | @Override 56 | public void init(OData odata, ServiceMetadata serviceMetadata) { 57 | this.odata = odata; 58 | this.serviceMetadata = serviceMetadata; 59 | } 60 | 61 | @Override 62 | public void readEntity(ODataRequest request, ODataResponse response, UriInfo uriInfo, ContentType responseFormat) throws ODataApplicationException, ODataLibraryException { 63 | 64 | Entity responseEntity = null; // required for serialization of the response body 65 | EdmEntitySet responseEdmEntitySet = null; // we need this for building the contextUrl 66 | 67 | // 1. retrieve the Entity Type 68 | // can be "normal" read operation, or navigation (to-one) 69 | //EdmEntitySet edmEntitySet = getUriResourceEdmEntitySet(uriInfo); 70 | List resourceParts = uriInfo.getUriResourceParts(); 71 | int segmentCount = resourceParts.size(); 72 | 73 | UriResource uriResource = resourceParts.get(0); 74 | if (!(uriResource instanceof UriResourceEntitySet)) { 75 | throw new ODataApplicationException("Only EntitySet is supported", HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ROOT); 76 | } 77 | 78 | UriResourceEntitySet uriResourceEntitySet = (UriResourceEntitySet) uriResource; 79 | EdmEntitySet startEdmEntitySet = uriResourceEntitySet.getEntitySet(); 80 | 81 | // Analyze the URI segments 82 | if (segmentCount == 1) { // no navigation 83 | responseEdmEntitySet = startEdmEntitySet; // since we have only one segment 84 | 85 | // 2. step: retrieve the data from backend 86 | List keyPredicates = getUriResourceKeyPredicates(uriInfo); 87 | 88 | EntityMetaData meta = this.entityMetaDataCollection.getEntityMetaDataByTypeSetName(responseEdmEntitySet.getName()); 89 | 90 | responseEntity = this.dataService.getEntityData(meta, keyPredicates); 91 | } else if (segmentCount == 2) { //navigation 92 | UriResource navSegment = resourceParts.get(1); 93 | 94 | if (navSegment instanceof UriResourceNavigation) { 95 | UriResourceNavigation uriResourceNavigation = (UriResourceNavigation) navSegment; 96 | EdmNavigationProperty edmNavigationProperty = uriResourceNavigation.getProperty(); 97 | 98 | responseEdmEntitySet = UriInfoUtil.getNavigationTargetEntitySet(startEdmEntitySet, edmNavigationProperty); 99 | 100 | // 2nd: fetch the data from backend. 101 | List keyPredicates = uriResourceEntitySet.getKeyPredicates(); 102 | 103 | EntityMetaData meta = this.entityMetaDataCollection.getEntityMetaDataByTypeSetName(startEdmEntitySet.getName()); 104 | 105 | Entity sourceEntity = this.dataService.getEntityData(meta, keyPredicates); 106 | 107 | responseEntity = this.dataService.getRelatedEntity(sourceEntity, uriResourceNavigation); 108 | } 109 | } else { 110 | // this would be the case for e.g. BookSet(1)/author/BookSet(1)/author 111 | throw new ODataApplicationException("Not supported", HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ROOT); 112 | } 113 | 114 | if (responseEntity == null) { 115 | throw new ODataApplicationException("Nothing found.", HttpStatusCode.NOT_FOUND.getStatusCode(), Locale.ROOT); 116 | } 117 | 118 | if (responseEdmEntitySet == null) { 119 | throw new ODataApplicationException("EntityType not found.", HttpStatusCode.NOT_FOUND.getStatusCode(), Locale.ROOT); 120 | } 121 | 122 | // 4. serialize 123 | EdmEntityType edmEntityType = responseEdmEntitySet.getEntityType(); 124 | 125 | ContextURL contextUrl = ContextURL.with().entitySet(responseEdmEntitySet).suffix(ContextURL.Suffix.ENTITY).build(); 126 | EntitySerializerOptions options = EntitySerializerOptions.with().contextURL(contextUrl).build(); 127 | 128 | ODataSerializer serializer = odata.createSerializer(responseFormat); 129 | SerializerResult serializerResult = serializer.entity(serviceMetadata, edmEntityType, responseEntity, options); 130 | 131 | //4. configure the response object 132 | setResponseContentAndOkStatus(response, serializerResult.getContent(), responseFormat); 133 | } 134 | 135 | @Override 136 | @SuppressWarnings("unchecked") 137 | public void createEntity(ODataRequest request, ODataResponse response, UriInfo uriInfo, 138 | ContentType requestFormat, ContentType responseFormat) throws ODataApplicationException, ODataLibraryException { 139 | 140 | // 1. Retrieve the entity type from the URI 141 | EdmEntitySet edmEntitySet = getUriResourceEdmEntitySet(uriInfo); 142 | EdmEntityType edmEntityType = edmEntitySet.getEntityType(); 143 | 144 | // 2. create the data in backend 145 | // 2.1. retrieve the payload from the POST request for the entity to create and deserialize it 146 | InputStream requestInputStream = request.getBody(); 147 | ODataDeserializer deserializer = this.odata.createDeserializer(requestFormat); 148 | DeserializerResult result = deserializer.entity(requestInputStream, edmEntityType); 149 | Entity requestEntity = result.getEntity(); 150 | // 2.2 do the creation in backend, which returns the newly created entity 151 | EntityMetaData meta = this.entityMetaDataCollection.getEntityMetaDataByTypeSetName(edmEntitySet.getName()); 152 | 153 | Entity createdEntity = this.dataService.createEntityData(meta, requestEntity); 154 | 155 | // 3. serialize the response (we have to return the created entity) 156 | ContextURL contextUrl = ContextURL.with().entitySet(edmEntitySet).build(); 157 | // expand and select currently not supported 158 | EntitySerializerOptions options = EntitySerializerOptions.with().contextURL(contextUrl).build(); 159 | 160 | ODataSerializer serializer = this.odata.createSerializer(responseFormat); 161 | SerializerResult serializedResponse = serializer.entity(serviceMetadata, edmEntityType, createdEntity, options); 162 | 163 | //4. configure the response object 164 | setResponseContentAndOkStatus(response, serializedResponse.getContent(), responseFormat); 165 | } 166 | 167 | @Override 168 | public void updateEntity(ODataRequest request, ODataResponse response, UriInfo uriInfo, 169 | ContentType requestFormat, ContentType responseFormat) throws ODataApplicationException, ODataLibraryException { 170 | 171 | // 1. Retrieve the entity set which belongs to the requested entity 172 | List resourcePaths = uriInfo.getUriResourceParts(); 173 | // Note: only in our example we can assume that the first segment is the EntitySet 174 | UriResourceEntitySet uriResourceEntitySet = (UriResourceEntitySet) resourcePaths.get(0); 175 | EdmEntitySet edmEntitySet = uriResourceEntitySet.getEntitySet(); 176 | EdmEntityType edmEntityType = edmEntitySet.getEntityType(); 177 | 178 | // 2. update the data in backend 179 | // 2.1. retrieve the payload from the PUT request for the entity to be updated 180 | InputStream requestInputStream = request.getBody(); 181 | ODataDeserializer deserializer = this.odata.createDeserializer(requestFormat); 182 | DeserializerResult result = deserializer.entity(requestInputStream, edmEntityType); 183 | Entity requestEntity = result.getEntity(); 184 | // 2.2 do the modification in backend 185 | List keyPredicates = uriResourceEntitySet.getKeyPredicates(); 186 | // Note that this updateEntity()-method is invoked for both PUT or PATCH operations 187 | HttpMethod httpMethod = request.getMethod(); 188 | 189 | EntityMetaData meta = this.entityMetaDataCollection.getEntityMetaDataByTypeSetName(edmEntitySet.getName()); 190 | 191 | this.dataService.updateEntityData(meta, keyPredicates, requestEntity, httpMethod); 192 | 193 | //3. configure the response object 194 | setResponseNoContentStatus(response); 195 | } 196 | 197 | @Override 198 | public void deleteEntity(ODataRequest request, ODataResponse response, UriInfo uriInfo) throws ODataApplicationException, ODataLibraryException { 199 | 200 | // 1. Retrieve the entity set which belongs to the requested entity 201 | List resourcePaths = uriInfo.getUriResourceParts(); 202 | // Note: only in our example we can assume that the first segment is the EntitySet 203 | UriResourceEntitySet uriResourceEntitySet = (UriResourceEntitySet) resourcePaths.get(0); 204 | EdmEntitySet edmEntitySet = uriResourceEntitySet.getEntitySet(); 205 | 206 | // 2. delete the data in backend 207 | EntityMetaData meta = this.entityMetaDataCollection.getEntityMetaDataByTypeSetName(edmEntitySet.getName()); 208 | 209 | List keyPredicates = uriResourceEntitySet.getKeyPredicates(); 210 | this.dataService.deleteEntityData(meta.getEntityClass(), keyPredicates); 211 | 212 | //3. configure the response object 213 | setResponseNoContentStatus(response); 214 | } 215 | 216 | } 217 | -------------------------------------------------------------------------------- /src/main/java/com/bloggingit/odata/olingo/v4/processor/DataPrimitiveProcessor.java: -------------------------------------------------------------------------------- 1 | package com.bloggingit.odata.olingo.v4.processor; 2 | 3 | import com.bloggingit.odata.olingo.edm.meta.EntityMetaData; 4 | import com.bloggingit.odata.olingo.edm.meta.EntityMetaDataContainer; 5 | import com.bloggingit.odata.olingo.v4.service.OlingoDataService; 6 | import java.io.InputStream; 7 | import java.util.List; 8 | import java.util.Locale; 9 | import org.apache.olingo.commons.api.data.ContextURL; 10 | import org.apache.olingo.commons.api.data.Entity; 11 | import org.apache.olingo.commons.api.data.Property; 12 | import org.apache.olingo.commons.api.edm.EdmEntitySet; 13 | import org.apache.olingo.commons.api.edm.EdmPrimitiveType; 14 | import org.apache.olingo.commons.api.edm.EdmProperty; 15 | import org.apache.olingo.commons.api.format.ContentType; 16 | import org.apache.olingo.commons.api.http.HttpStatusCode; 17 | import org.apache.olingo.server.api.OData; 18 | import org.apache.olingo.server.api.ODataApplicationException; 19 | import org.apache.olingo.server.api.ODataLibraryException; 20 | import org.apache.olingo.server.api.ODataRequest; 21 | import org.apache.olingo.server.api.ODataResponse; 22 | import org.apache.olingo.server.api.ServiceMetadata; 23 | import org.apache.olingo.server.api.processor.PrimitiveProcessor; 24 | import org.apache.olingo.server.api.serializer.ODataSerializer; 25 | import org.apache.olingo.server.api.serializer.PrimitiveSerializerOptions; 26 | import org.apache.olingo.server.api.serializer.SerializerResult; 27 | import org.apache.olingo.server.api.uri.UriInfo; 28 | import org.apache.olingo.server.api.uri.UriParameter; 29 | 30 | /** 31 | * This class is invoked by the Apache Olingo framework when the the OData 32 | * service is invoked order to display primitive property data of a entity. 33 | */ 34 | public class DataPrimitiveProcessor extends AbstractEntityMetaDataProcessor implements PrimitiveProcessor { 35 | 36 | private OData odata; 37 | private ServiceMetadata serviceMetadata; 38 | private final EntityMetaDataContainer entityMetaDataCollection; 39 | 40 | private final OlingoDataService dataService; 41 | 42 | public DataPrimitiveProcessor(OlingoDataService dataService, EntityMetaDataContainer entityMetaDataCollection) { 43 | this.dataService = dataService; 44 | this.entityMetaDataCollection = entityMetaDataCollection; 45 | } 46 | 47 | @Override 48 | public void init(OData odata, ServiceMetadata serviceMetadata) { 49 | this.odata = odata; 50 | this.serviceMetadata = serviceMetadata; 51 | } 52 | 53 | @Override 54 | public void readPrimitive(ODataRequest request, ODataResponse response, UriInfo uriInfo, ContentType responseFormat) throws ODataApplicationException, ODataLibraryException { 55 | // 1. Retrieve info from URI 56 | // 1.1. retrieve the info about the requested entity set 57 | EdmEntitySet edmEntitySet = getUriResourceEdmEntitySet(uriInfo); 58 | // the key for the entity 59 | List keyPredicates = getUriResourceKeyPredicates(uriInfo); 60 | 61 | // 1.2. retrieve the requested (Edm) property 62 | EdmProperty edmProperty = getUriResourceEdmProperty(uriInfo); 63 | String edmPropertyName = edmProperty.getName(); 64 | // in our example, we know we have only primitive types in our model 65 | EdmPrimitiveType edmPropertyType = (EdmPrimitiveType) edmProperty.getType(); 66 | 67 | // 2. retrieve data from backend 68 | // 2.1. retrieve the entity data, for which the property has to be read 69 | EntityMetaData meta = this.entityMetaDataCollection.getEntityMetaDataByTypeSetName(edmEntitySet.getName()); 70 | 71 | Entity entity = this.dataService.getEntityData(meta, keyPredicates); 72 | 73 | if (entity == null) { // Bad request 74 | throw new ODataApplicationException("Entity not found", 75 | HttpStatusCode.NOT_FOUND.getStatusCode(), Locale.ENGLISH); 76 | } 77 | 78 | // 2.2. retrieve the property data from the entity 79 | Property property = entity.getProperty(edmPropertyName); 80 | if (property == null) { 81 | throw new ODataApplicationException("Property not found", 82 | HttpStatusCode.NOT_FOUND.getStatusCode(), Locale.ENGLISH); 83 | } 84 | 85 | // 3. serialize 86 | Object value = property.getValue(); 87 | if (value != null) { 88 | // 3.1. configure the serializer 89 | ODataSerializer serializer = odata.createSerializer(responseFormat); 90 | 91 | ContextURL contextUrl = ContextURL.with().entitySet(edmEntitySet).navOrPropertyPath(edmPropertyName).build(); 92 | PrimitiveSerializerOptions options = PrimitiveSerializerOptions.with().contextURL(contextUrl).build(); 93 | // 3.2. serialize 94 | SerializerResult serializerResult = serializer.primitive(serviceMetadata, edmPropertyType, property, options); 95 | InputStream propertyStream = serializerResult.getContent(); 96 | 97 | //4. configure the response object 98 | setResponseContentAndOkStatus(response, propertyStream, responseFormat); 99 | } else { 100 | // in case there's no value for the property, we can skip the serialization 101 | setResponseNoContentStatus(response); 102 | } 103 | } 104 | 105 | @Override 106 | public void updatePrimitive(ODataRequest odr, ODataResponse odr1, UriInfo ui, ContentType ct, ContentType ct1) throws ODataApplicationException, ODataLibraryException { 107 | throw new UnsupportedOperationException("Not supported yet."); 108 | } 109 | 110 | @Override 111 | public void deletePrimitive(ODataRequest odr, ODataResponse odr1, UriInfo ui) throws ODataApplicationException, ODataLibraryException { 112 | throw new UnsupportedOperationException("Not supported yet."); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/main/java/com/bloggingit/odata/olingo/v4/processor/DataPrimitiveValueProcessor.java: -------------------------------------------------------------------------------- 1 | package com.bloggingit.odata.olingo.v4.processor; 2 | 3 | import com.bloggingit.odata.olingo.edm.meta.EntityMetaData; 4 | import com.bloggingit.odata.olingo.edm.meta.EntityMetaDataContainer; 5 | import com.bloggingit.odata.olingo.v4.service.OlingoDataService; 6 | import java.io.ByteArrayInputStream; 7 | import java.io.InputStream; 8 | import java.nio.charset.Charset; 9 | import java.util.List; 10 | import java.util.Locale; 11 | import org.apache.olingo.commons.api.data.ContextURL; 12 | import org.apache.olingo.commons.api.data.Entity; 13 | import org.apache.olingo.commons.api.data.Property; 14 | import org.apache.olingo.commons.api.edm.EdmEntitySet; 15 | import org.apache.olingo.commons.api.edm.EdmPrimitiveType; 16 | import org.apache.olingo.commons.api.edm.EdmProperty; 17 | import org.apache.olingo.commons.api.format.ContentType; 18 | import org.apache.olingo.commons.api.http.HttpStatusCode; 19 | import org.apache.olingo.server.api.OData; 20 | import org.apache.olingo.server.api.ODataApplicationException; 21 | import org.apache.olingo.server.api.ODataLibraryException; 22 | import org.apache.olingo.server.api.ODataRequest; 23 | import org.apache.olingo.server.api.ODataResponse; 24 | import org.apache.olingo.server.api.ServiceMetadata; 25 | import org.apache.olingo.server.api.processor.PrimitiveValueProcessor; 26 | import org.apache.olingo.server.api.serializer.ODataSerializer; 27 | import org.apache.olingo.server.api.serializer.PrimitiveSerializerOptions; 28 | import org.apache.olingo.server.api.serializer.SerializerResult; 29 | import org.apache.olingo.server.api.uri.UriInfo; 30 | import org.apache.olingo.server.api.uri.UriParameter; 31 | import org.apache.olingo.server.api.uri.UriResourceProperty; 32 | 33 | /** 34 | * This class is invoked by the Apache Olingo framework when the the OData 35 | * service is invoked order to display primitive property data of a entity. 36 | */ 37 | public class DataPrimitiveValueProcessor extends AbstractEntityMetaDataProcessor implements PrimitiveValueProcessor { 38 | 39 | private OData odata; 40 | private ServiceMetadata serviceMetadata; 41 | private final EntityMetaDataContainer entityMetaDataCollection; 42 | 43 | private final OlingoDataService dataService; 44 | 45 | public DataPrimitiveValueProcessor(OlingoDataService dataService, EntityMetaDataContainer entityMetaDataCollection) { 46 | this.dataService = dataService; 47 | this.entityMetaDataCollection = entityMetaDataCollection; 48 | } 49 | 50 | @Override 51 | public void init(OData odata, ServiceMetadata serviceMetadata) { 52 | this.odata = odata; 53 | this.serviceMetadata = serviceMetadata; 54 | } 55 | 56 | @Override 57 | public void readPrimitiveValue(ODataRequest request, ODataResponse response, UriInfo uriInfo, ContentType responseFormat) throws ODataApplicationException, ODataLibraryException { 58 | // 1. Retrieve info from URI 59 | // 1.1. retrieve the info about the requested entity set 60 | EdmEntitySet edmEntitySet = getUriResourceEdmEntitySet(uriInfo); 61 | // the key for the entity 62 | List keyPredicates = getUriResourceKeyPredicates(uriInfo); 63 | 64 | // 1.2. retrieve the requested (Edm) property 65 | // Note: only in our example we can rely that the second segment is the is the Property 66 | UriResourceProperty uriProperty = (UriResourceProperty) uriInfo.getUriResourceParts().get(1); 67 | EdmProperty edmProperty = uriProperty.getProperty(); 68 | String edmPropertyName = edmProperty.getName(); 69 | 70 | // 2. retrieve data from backend 71 | // 2.1. retrieve the entity data, for which the property has to be read 72 | EntityMetaData meta = this.entityMetaDataCollection.getEntityMetaDataByTypeSetName(edmEntitySet.getName()); 73 | 74 | Entity entity = this.dataService.getEntityData(meta, keyPredicates); 75 | 76 | if (entity == null) { // Bad request 77 | throw new ODataApplicationException("Entity not found", 78 | HttpStatusCode.NOT_FOUND.getStatusCode(), Locale.ENGLISH); 79 | } 80 | 81 | // 2.2. retrieve the property data from the entity 82 | Property property = entity.getProperty(edmPropertyName); 83 | if (property == null) { 84 | throw new ODataApplicationException("Property not found", 85 | HttpStatusCode.NOT_FOUND.getStatusCode(), Locale.ENGLISH); 86 | } 87 | 88 | // 3. serialize 89 | Object value = property.getValue(); 90 | if (value != null) { 91 | String valueStr = String.valueOf(value); 92 | ByteArrayInputStream serializerContent = new ByteArrayInputStream( 93 | valueStr.getBytes(Charset.forName("UTF-8"))); 94 | 95 | // configure the response object 96 | setResponseContentAndOkStatus(response, serializerContent, ContentType.TEXT_PLAIN); 97 | } else { 98 | // in case there's no value for the property, we can skip the serialization 99 | setResponseNoContentStatus(response); 100 | } 101 | } 102 | 103 | @Override 104 | public void updatePrimitiveValue(ODataRequest odr, ODataResponse odr1, UriInfo ui, ContentType ct, ContentType ct1) throws ODataApplicationException, ODataLibraryException { 105 | throw new UnsupportedOperationException("Not supported yet."); 106 | } 107 | 108 | @Override 109 | public void deletePrimitiveValue(ODataRequest odr, ODataResponse odr1, UriInfo ui) throws ODataApplicationException, ODataLibraryException { 110 | throw new UnsupportedOperationException("Not supported yet."); 111 | } 112 | 113 | @Override 114 | public void readPrimitive(ODataRequest request, ODataResponse response, UriInfo uriInfo, ContentType responseFormat) throws ODataApplicationException, ODataLibraryException { 115 | // 1. Retrieve info from URI 116 | // 1.1. retrieve the info about the requested entity set 117 | EdmEntitySet edmEntitySet = getUriResourceEdmEntitySet(uriInfo); 118 | // the key for the entity 119 | List keyPredicates = getUriResourceKeyPredicates(uriInfo); 120 | 121 | // 1.2. retrieve the requested (Edm) property 122 | EdmProperty edmProperty = getUriResourceEdmProperty(uriInfo); 123 | String edmPropertyName = edmProperty.getName(); 124 | // in our example, we know we have only primitive types in our model 125 | EdmPrimitiveType edmPropertyType = (EdmPrimitiveType) edmProperty.getType(); 126 | 127 | // 2. retrieve data from backend 128 | // 2.1. retrieve the entity data, for which the property has to be read 129 | EntityMetaData meta = this.entityMetaDataCollection.getEntityMetaDataByTypeSetName(edmEntitySet.getName()); 130 | 131 | Entity entity = this.dataService.getEntityData(meta, keyPredicates); 132 | 133 | if (entity == null) { // Bad request 134 | throw new ODataApplicationException("Entity not found", 135 | HttpStatusCode.NOT_FOUND.getStatusCode(), Locale.ENGLISH); 136 | } 137 | 138 | // 2.2. retrieve the property data from the entity 139 | Property property = entity.getProperty(edmPropertyName); 140 | if (property == null) { 141 | throw new ODataApplicationException("Property not found", 142 | HttpStatusCode.NOT_FOUND.getStatusCode(), Locale.ENGLISH); 143 | } 144 | 145 | // 3. serialize 146 | Object value = property.getValue(); 147 | if (value != null) { 148 | // 3.1. configure the serializer 149 | ODataSerializer serializer = odata.createSerializer(responseFormat); 150 | 151 | ContextURL contextUrl = ContextURL.with().entitySet(edmEntitySet).navOrPropertyPath(edmPropertyName).build(); 152 | PrimitiveSerializerOptions options = PrimitiveSerializerOptions.with().contextURL(contextUrl).build(); 153 | // 3.2. serialize 154 | SerializerResult serializerResult = serializer.primitive(serviceMetadata, edmPropertyType, property, options); 155 | InputStream propertyStream = serializerResult.getContent(); 156 | 157 | //4. configure the response object 158 | setResponseContentAndOkStatus(response, propertyStream, responseFormat); 159 | } else { 160 | // in case there's no value for the property, we can skip the serialization 161 | setResponseNoContentStatus(response); 162 | } 163 | } 164 | 165 | @Override 166 | public void updatePrimitive(ODataRequest odr, ODataResponse odr1, UriInfo ui, ContentType ct, ContentType ct1) throws ODataApplicationException, ODataLibraryException { 167 | throw new UnsupportedOperationException("Not supported yet."); 168 | } 169 | 170 | @Override 171 | public void deletePrimitive(ODataRequest odr, ODataResponse odr1, UriInfo ui) throws ODataApplicationException, ODataLibraryException { 172 | throw new UnsupportedOperationException("Not supported yet."); 173 | } 174 | 175 | } 176 | -------------------------------------------------------------------------------- /src/main/java/com/bloggingit/odata/olingo/v4/provider/AnnotationEdmProvider.java: -------------------------------------------------------------------------------- 1 | package com.bloggingit.odata.olingo.v4.provider; 2 | 3 | import com.bloggingit.odata.olingo.v4.mapper.OlingoObjectMapper; 4 | import com.bloggingit.odata.olingo.edm.meta.EntityMetaData; 5 | import com.bloggingit.odata.olingo.edm.meta.EntityMetaDataContainer; 6 | import com.bloggingit.odata.olingo.edm.meta.EntityMetaDataBuilder; 7 | import com.bloggingit.odata.olingo.edm.meta.EntityMetaProperty; 8 | import com.bloggingit.odata.olingo.v4.factory.OlingoObjectFactory; 9 | import com.bloggingit.odata.olingo.v4.mapper.FQNMapper; 10 | import java.util.ArrayList; 11 | import java.util.Arrays; 12 | import java.util.List; 13 | import lombok.Getter; 14 | 15 | import org.apache.olingo.commons.api.edm.FullQualifiedName; 16 | import org.apache.olingo.commons.api.edm.provider.CsdlAbstractEdmProvider; 17 | import org.apache.olingo.commons.api.edm.provider.CsdlComplexType; 18 | import org.apache.olingo.commons.api.edm.provider.CsdlEntityContainer; 19 | import org.apache.olingo.commons.api.edm.provider.CsdlEntityContainerInfo; 20 | import org.apache.olingo.commons.api.edm.provider.CsdlEntitySet; 21 | import org.apache.olingo.commons.api.edm.provider.CsdlEntityType; 22 | import org.apache.olingo.commons.api.edm.provider.CsdlEnumType; 23 | import org.apache.olingo.commons.api.edm.provider.CsdlSchema; 24 | import org.apache.olingo.commons.api.edm.provider.CsdlStructuralType; 25 | import org.apache.olingo.commons.api.ex.ODataException; 26 | 27 | /** 28 | * Provider-implementation of a {@link CsdlAbstractEdmProvider} for annotation 29 | * support in the entity data model. 30 | */ 31 | public class AnnotationEdmProvider extends CsdlAbstractEdmProvider { 32 | 33 | @Getter 34 | private final EntityMetaDataContainer entityMetaDataContainer; 35 | 36 | public AnnotationEdmProvider(String serviceNamespace, String edmContainer, String dtoPackage) { 37 | this.entityMetaDataContainer = EntityMetaDataBuilder.createContainer(serviceNamespace, edmContainer, dtoPackage); 38 | } 39 | 40 | @Override 41 | public List getSchemas() { 42 | List entityTypes = new ArrayList<>(); 43 | List enumTypes = new ArrayList<>(); 44 | List complexTypes = new ArrayList<>(); 45 | 46 | String serviceNamespace = this.entityMetaDataContainer.getServiceNamespace(); 47 | 48 | this.entityMetaDataContainer.getAllEntityMetaData().forEach((meta) -> { 49 | CsdlStructuralType structuralType = OlingoObjectMapper.mapEntityMetaDataToCsdlStructuralType(meta, serviceNamespace); 50 | 51 | if (structuralType instanceof CsdlComplexType) { 52 | complexTypes.add((CsdlComplexType) structuralType); 53 | } else { 54 | entityTypes.add((CsdlEntityType) structuralType); 55 | } 56 | 57 | enumTypes.addAll(OlingoObjectMapper.mapEntityMetaDataToCsdlEnumTypeList(meta)); 58 | }); 59 | 60 | CsdlSchema schema = OlingoObjectFactory 61 | .createCsdlSchema(serviceNamespace, getEntityContainer(), entityTypes, enumTypes, complexTypes); 62 | 63 | return Arrays.asList(schema); 64 | } 65 | 66 | @Override 67 | @SuppressWarnings("unchecked") 68 | public CsdlEnumType getEnumType(FullQualifiedName enumTypeNameFQ) throws ODataException { 69 | CsdlEnumType csdlEnumType = null; 70 | if (enumTypeNameFQ != null 71 | && this.entityMetaDataContainer.getServiceNamespace().equals(enumTypeNameFQ.getNamespace())) { 72 | 73 | EntityMetaProperty propertyData = this.entityMetaDataContainer.getEntityMetaPropertyDataByTypeName(enumTypeNameFQ.getNamespace(), enumTypeNameFQ.getName()); 74 | 75 | if (propertyData != null) { 76 | Class enumFieldType = (Class) propertyData.getFieldType(); 77 | csdlEnumType = OlingoObjectFactory.createEnumType(enumFieldType); 78 | } 79 | } 80 | 81 | return csdlEnumType; 82 | } 83 | 84 | @Override 85 | public CsdlEntityType getEntityType(FullQualifiedName entityTypeNameFQ) { 86 | EntityMetaData metaData = this.entityMetaDataContainer.getEntityMetaDataByTypeName(entityTypeNameFQ.getNamespace(), entityTypeNameFQ.getName()); 87 | 88 | return (CsdlEntityType) OlingoObjectMapper.mapEntityMetaDataToCsdlStructuralType(metaData, this.entityMetaDataContainer.getServiceNamespace()); 89 | } 90 | 91 | @Override 92 | public CsdlEntitySet getEntitySet(FullQualifiedName entityContainerFQ, String entitySetName) { 93 | CsdlEntitySet entitySet = null; 94 | 95 | if (entityContainerFQ != null 96 | && this.entityMetaDataContainer.getServiceNamespace().equals(entityContainerFQ.getNamespace()) 97 | && this.entityMetaDataContainer.getEdmContainerName().equals(entityContainerFQ.getName())) { 98 | 99 | EntityMetaData meta = this.entityMetaDataContainer.getEntityMetaDataByTypeSetName(entitySetName); 100 | if (meta != null) { 101 | entitySet = OlingoObjectFactory.createCsdlEntitySet(meta, entityContainerFQ.getNamespace()); 102 | } 103 | } 104 | 105 | return entitySet; 106 | } 107 | 108 | @Override 109 | public CsdlComplexType getComplexType(FullQualifiedName complexTypeNameFQ) throws ODataException { 110 | EntityMetaData metaData = this.entityMetaDataContainer.getEntityMetaDataByTypeName(complexTypeNameFQ.getNamespace(), complexTypeNameFQ.getName()); 111 | 112 | if (metaData.isComplexType()) { 113 | return (CsdlComplexType) OlingoObjectMapper.mapEntityMetaDataToCsdlStructuralType(metaData, this.entityMetaDataContainer.getServiceNamespace()); 114 | } else { 115 | return null; 116 | } 117 | } 118 | 119 | 120 | @Override 121 | public CsdlEntityContainer getEntityContainer() { 122 | List entitySets = new ArrayList<>(); 123 | 124 | this.entityMetaDataContainer.getAllEntityMetaData().forEach((metaData) -> { 125 | FullQualifiedName containerNameFQ = FQNMapper.createFullQualifiedName(this.entityMetaDataContainer); 126 | 127 | CsdlEntitySet entitySet = getEntitySet(containerNameFQ, metaData.getEntityTypeSetName()); 128 | if (entitySet != null) { 129 | entitySets.add(entitySet); 130 | } 131 | }); 132 | 133 | return OlingoObjectFactory.createCsdlEntityContainer(this.entityMetaDataContainer.getEdmContainerName(), entitySets); 134 | } 135 | 136 | @Override 137 | public CsdlEntityContainerInfo getEntityContainerInfo(FullQualifiedName entityContainerFQ) { 138 | String serviceNamespace; 139 | String edmContainerName; 140 | 141 | if (entityContainerFQ == null) { 142 | serviceNamespace = this.entityMetaDataContainer.getServiceNamespace(); 143 | edmContainerName = this.entityMetaDataContainer.getEdmContainerName(); 144 | } else { 145 | serviceNamespace = entityContainerFQ.getNamespace(); 146 | edmContainerName = entityContainerFQ.getName(); 147 | } 148 | 149 | return OlingoObjectFactory.createCsdlEntityContainerInfo(serviceNamespace, edmContainerName); 150 | } 151 | 152 | } 153 | -------------------------------------------------------------------------------- /src/main/java/com/bloggingit/odata/olingo/v4/service/OlingoDataService.java: -------------------------------------------------------------------------------- 1 | package com.bloggingit.odata.olingo.v4.service; 2 | 3 | import com.bloggingit.odata.model.BaseEntity; 4 | import com.bloggingit.odata.exception.EntityDataException; 5 | import com.bloggingit.odata.olingo.v4.mapper.OlingoObjectMapper; 6 | import com.bloggingit.odata.olingo.edm.meta.EntityMetaData; 7 | import com.bloggingit.odata.olingo.edm.meta.EntityMetaProperty; 8 | import com.bloggingit.odata.storage.InMemoryDataStorage; 9 | import java.io.Serializable; 10 | import java.util.HashMap; 11 | import java.util.List; 12 | import java.util.Locale; 13 | import java.util.Map; 14 | import javax.ejb.LocalBean; 15 | import javax.ejb.Stateless; 16 | import org.apache.olingo.commons.api.data.Entity; 17 | import org.apache.olingo.commons.api.data.EntityCollection; 18 | import org.apache.olingo.commons.api.data.Link; 19 | import org.apache.olingo.commons.api.data.Property; 20 | import org.apache.olingo.commons.api.edm.EdmNavigationProperty; 21 | import org.apache.olingo.commons.api.http.HttpMethod; 22 | import org.apache.olingo.commons.api.http.HttpStatusCode; 23 | import org.apache.olingo.server.api.ODataApplicationException; 24 | import org.apache.olingo.server.api.uri.UriParameter; 25 | import org.apache.olingo.server.api.uri.UriResourceNavigation; 26 | 27 | /** 28 | * This service provides the methods for the OData service to read and store 29 | * data. 30 | * 31 | * Internally the in-memory data storage will be used. 32 | */ 33 | @Stateless 34 | @LocalBean 35 | public class OlingoDataService implements Serializable { 36 | 37 | private static final long serialVersionUID = 1L; 38 | 39 | public EntityCollection getEntityDataList(EntityMetaData entityMetaData) { 40 | List entityDataList = InMemoryDataStorage.getDataListByBaseEntityClass(entityMetaData.getEntityClass()); 41 | 42 | return OlingoObjectMapper.mapObjectEntitiesToOlingoEntityCollection(entityDataList, entityMetaData); 43 | 44 | } 45 | 46 | public Entity getEntityData(EntityMetaData entityMetaData, List keyParams) { 47 | long id = Long.parseLong(keyParams.get(0).getText()); 48 | T baseEntity = InMemoryDataStorage.getDataByClassAndId(entityMetaData.getEntityClass(), id); 49 | return (baseEntity != null) ? OlingoObjectMapper.mapObjEntityToOlingoEntity(entityMetaData, baseEntity) : null; 50 | } 51 | 52 | @SuppressWarnings("unchecked") 53 | public void deleteEntityData(Class entityClass, List keyParams) { 54 | long id = Long.parseLong(keyParams.get(0).getText()); 55 | 56 | InMemoryDataStorage.deleteDataByClassAndId((Class) entityClass, id); 57 | } 58 | 59 | public Entity createEntityData(EntityMetaData entityMetaData, Entity requestEntity) throws ODataApplicationException { 60 | 61 | T baseEntity = OlingoObjectMapper.mapOlingoEntityToObjectEntity(entityMetaData, requestEntity); 62 | T newBaseEntity; 63 | try { 64 | newBaseEntity = InMemoryDataStorage.createEntity(baseEntity); 65 | } catch (EntityDataException ex) { 66 | throw new ODataApplicationException("Entity not found", 67 | HttpStatusCode.NOT_FOUND.getStatusCode(), Locale.ENGLISH, ex); 68 | } 69 | 70 | return OlingoObjectMapper.mapObjEntityToOlingoEntity(entityMetaData, newBaseEntity); 71 | } 72 | 73 | public Entity getRelatedEntity(Entity entity, UriResourceNavigation navigationResource) 74 | throws ODataApplicationException { 75 | 76 | final EdmNavigationProperty edmNavigationProperty = navigationResource.getProperty(); 77 | 78 | //if (edmNavigationProperty.isCollection()) { 79 | // return Util.findEntity(edmNavigationProperty.getType(), getRelatedEntityCollection(entity, navigationResource), 80 | // navigationResource.getKeyPredicates()); 81 | //} else { 82 | final Link link = entity.getNavigationLink(edmNavigationProperty.getName()); 83 | return link == null ? null : link.getInlineEntity(); 84 | // } 85 | } 86 | 87 | public void updateEntityData(EntityMetaData entityMetaData, List keyParams, Entity entity, HttpMethod httpMethod) throws ODataApplicationException { 88 | long id = Long.parseLong(keyParams.get(0).getText()); 89 | 90 | Map newPropertiesAndValues = new HashMap<>(); 91 | 92 | // depending on the HttpMethod, our behavior is different 93 | // in case of PATCH, the existing property is not touched, do nothing 94 | //in case of PUT, the existing property is set to null 95 | boolean nullableUnkownProperties = (httpMethod.equals(HttpMethod.PUT)); 96 | 97 | List metaProperties = entityMetaData.getProperties(); 98 | 99 | metaProperties.forEach((metaProp) -> { 100 | Property newProperty = entity.getProperty(metaProp.getEdmName()); 101 | 102 | if (newProperty != null && !metaProp.isKey()) { 103 | Object val = OlingoObjectMapper.mapOlingoPropertyToObjValue(newProperty, metaProp); 104 | newPropertiesAndValues.put(metaProp.getFieldName(), val); 105 | } else if (nullableUnkownProperties && !metaProp.isKey()) { 106 | // if a property has NOT been added to the request payload 107 | // depending on the HttpMethod, our behavior is different 108 | // in case of PUT, the existing property is set to null 109 | // in case of PATCH, the existing property is not touched, do nothing 110 | newPropertiesAndValues.put(metaProp.getFieldName(), metaProp.getDefaultValue()); 111 | } 112 | }); 113 | 114 | try { 115 | InMemoryDataStorage.updateEntity(entityMetaData.getEntityClass(), id, newPropertiesAndValues, nullableUnkownProperties); 116 | } catch (EntityDataException ex) { 117 | throw new ODataApplicationException("Entity not found", 118 | HttpStatusCode.NOT_FOUND.getStatusCode(), Locale.ENGLISH, ex); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/com/bloggingit/odata/olingo/v4/servlet/ODataDemoServlet.java: -------------------------------------------------------------------------------- 1 | package com.bloggingit.odata.olingo.v4.servlet; 2 | 3 | import java.io.IOException; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | import javax.servlet.ServletConfig; 7 | import javax.servlet.ServletException; 8 | import javax.servlet.annotation.WebServlet; 9 | import javax.servlet.http.HttpServlet; 10 | import javax.servlet.http.HttpServletRequest; 11 | import javax.servlet.http.HttpServletResponse; 12 | import org.apache.olingo.server.api.OData; 13 | import org.apache.olingo.server.api.ODataHttpHandler; 14 | import org.apache.olingo.server.api.ServiceMetadata; 15 | 16 | import com.bloggingit.odata.olingo.v4.provider.AnnotationEdmProvider; 17 | import com.bloggingit.odata.olingo.v4.processor.DataCollectionProcessor; 18 | import com.bloggingit.odata.olingo.v4.processor.DataEntityProcessor; 19 | import com.bloggingit.odata.olingo.v4.processor.DataPrimitiveValueProcessor; 20 | import com.bloggingit.odata.olingo.v4.service.OlingoDataService; 21 | import javax.inject.Inject; 22 | import javax.servlet.Servlet; 23 | import org.apache.olingo.commons.api.edmx.EdmxReference; 24 | import org.apache.olingo.server.api.debug.DefaultDebugSupport; 25 | 26 | /** 27 | * This {@link Servlet} provides the OData service. 28 | * 29 | * @author mes 30 | */ 31 | @WebServlet(name = ODataDemoServlet.SERVLET_NAME, urlPatterns = {ODataDemoServlet.SERVLET_URL_PATTERNS}) 32 | public class ODataDemoServlet extends HttpServlet { 33 | 34 | private static final long serialVersionUID = 1L; 35 | 36 | public static final String SERVLET_NAME = "ODataDemoServlet"; 37 | 38 | public static final String SERVLET_URL_PATTERNS = "/api/servlet/v1/odatademo.svc/*"; 39 | 40 | private static final String BASE_MODEL_PACKAGE = "com.bloggingit.odata.model"; 41 | 42 | public static final String SERVICE_NAMESPACE = "OData"; 43 | 44 | public static final String EDM_CONTAINER_NAME = "Container"; 45 | 46 | private transient AnnotationEdmProvider edmProvider; 47 | private transient DataCollectionProcessor entityCollectionProcessor; 48 | private transient DataEntityProcessor entityProcessor; 49 | //private transient DataPrimitiveProcessor entityDataPrimitiveProcessor; 50 | private transient DataPrimitiveValueProcessor entityDataPrimitiveValueProcessor; 51 | 52 | @Inject 53 | private OlingoDataService dataService; 54 | 55 | @Override 56 | public void init(ServletConfig config) throws ServletException { 57 | super.init(config); 58 | 59 | this.edmProvider = new AnnotationEdmProvider(SERVICE_NAMESPACE, EDM_CONTAINER_NAME, BASE_MODEL_PACKAGE); 60 | this.entityCollectionProcessor = new DataCollectionProcessor(dataService, this.edmProvider.getEntityMetaDataContainer()); 61 | this.entityProcessor = new DataEntityProcessor(dataService, this.edmProvider.getEntityMetaDataContainer()); 62 | // this.entityDataPrimitiveProcessor = new DataPrimitiveProcessor(dataService, this.edmProvider.getEntityMetaDataCollection()); 63 | this.entityDataPrimitiveValueProcessor = new DataPrimitiveValueProcessor(dataService, this.edmProvider.getEntityMetaDataContainer()); 64 | } 65 | 66 | @Override 67 | protected void service(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { 68 | 69 | List odataProcessors = new ArrayList<>(); 70 | OData odata = OData.newInstance(); 71 | ServiceMetadata edm = odata.createServiceMetadata(this.edmProvider, odataProcessors); 72 | ODataHttpHandler handler = odata.createHandler(edm); 73 | handler.register(entityCollectionProcessor); 74 | handler.register(entityProcessor); 75 | //handler.register(entityDataPrimitiveProcessor); 76 | handler.register(entityDataPrimitiveValueProcessor); 77 | 78 | //run service url query option odata-debug=json to return detailed error information in json format for each request. 79 | //http://olingo.apache.org/doc/odata2/tutorials/debug.html 80 | handler.register(new DefaultDebugSupport()); 81 | 82 | handler.process(req, resp); 83 | 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/com/bloggingit/odata/olingo/v4/util/DefaultValue.java: -------------------------------------------------------------------------------- 1 | package com.bloggingit.odata.olingo.v4.util; 2 | 3 | import java.lang.reflect.Array; 4 | import java.util.Map; 5 | import java.util.stream.Collectors; 6 | import java.util.stream.Stream; 7 | 8 | /** 9 | * Provides the default value of any primitive type by creating an array of one 10 | * element and retrieving its first value. 11 | */ 12 | public class DefaultValue { 13 | 14 | /** 15 | * Map with primitive types 16 | */ 17 | private static final Map, Object> DEFAULT_VALUES = Stream 18 | .of(boolean.class, byte.class, char.class, double.class, float.class, int.class, long.class, short.class) 19 | .collect(Collectors.toMap(clazz -> (Class) clazz, clazz -> Array.get(Array.newInstance(clazz, 1), 0))); 20 | 21 | /** 22 | * Returns the default value for the given class. 23 | * 24 | * @param the generic type of the class 25 | * @param clazz the class for which a default value is needed 26 | * @return A reasonable default value for the given class (the boxed default 27 | * value for primitives, null otherwise). 28 | */ 29 | @SuppressWarnings("unchecked") 30 | public static T forClass(Class clazz) { 31 | return (T) DEFAULT_VALUES.get(clazz); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/bloggingit/odata/olingo/v4/util/ReflectionUtils.java: -------------------------------------------------------------------------------- 1 | package com.bloggingit.odata.olingo.v4.util; 2 | 3 | import java.beans.IntrospectionException; 4 | import java.beans.PropertyDescriptor; 5 | import java.lang.annotation.Annotation; 6 | import java.lang.reflect.InvocationTargetException; 7 | import java.lang.reflect.Method; 8 | import java.util.Set; 9 | import org.apache.olingo.commons.api.ex.ODataRuntimeException; 10 | import org.reflections.Reflections; 11 | 12 | /** 13 | * Simple utility class with convenient helper methods for simple working with 14 | * the java reflection API. 15 | */ 16 | public class ReflectionUtils { 17 | 18 | /** 19 | * Get classes annotated with a given annotation in a certain package. 20 | * 21 | * @param dtoPackage search in this certain package 22 | * @param annotationClass find classes annotated with this annotation 23 | * @return the classes with the given annotation 24 | */ 25 | public static Set> findClassesInPackageAnnotatedWith(String dtoPackage, Class annotationClass) { 26 | Reflections reflections = new Reflections(dtoPackage); 27 | 28 | Set> classes = reflections.getTypesAnnotatedWith(annotationClass); 29 | 30 | return classes; 31 | 32 | } 33 | 34 | /** 35 | * Invoke the getter method of the given property for the specified object 36 | * 37 | * @param propertyName invoke the getter of this property 38 | * @param entity invoke the getter for this object 39 | * @return the result of the getter method 40 | */ 41 | public static Object invokePropertyGetter(String propertyName, Object entity) { 42 | try { 43 | return new PropertyDescriptor(propertyName, entity.getClass()).getReadMethod().invoke(entity); //invoke getter 44 | } catch (IntrospectionException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { 45 | throw new ODataRuntimeException(ex); 46 | } 47 | } 48 | 49 | /** 50 | * Invoke the setter method of the given property for the specified object 51 | * and sets the given value 52 | * 53 | * @param propertyName invoke the setter of this property 54 | * @param entity invoke the setter for this object 55 | * @param val the new value for this property 56 | */ 57 | public static void invokePropertySetter(String propertyName, Object entity, Object val) { 58 | try { 59 | Method setMethod = new PropertyDescriptor(propertyName, entity.getClass()).getWriteMethod(); // get setter 60 | setMethod.invoke(entity, val); //invoke setter 61 | } catch (IntrospectionException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NullPointerException ex) { 62 | throw new ODataRuntimeException(ex); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/bloggingit/odata/olingo/v4/util/UriInfoUtil.java: -------------------------------------------------------------------------------- 1 | package com.bloggingit.odata.olingo.v4.util; 2 | 3 | import java.util.List; 4 | import java.util.Locale; 5 | import org.apache.olingo.commons.api.edm.EdmBindingTarget; 6 | import org.apache.olingo.commons.api.edm.EdmEntitySet; 7 | import org.apache.olingo.commons.api.edm.EdmNavigationProperty; 8 | import org.apache.olingo.commons.api.http.HttpStatusCode; 9 | import org.apache.olingo.server.api.ODataApplicationException; 10 | import org.apache.olingo.server.api.uri.UriInfoResource; 11 | import org.apache.olingo.server.api.uri.UriResource; 12 | import org.apache.olingo.server.api.uri.UriResourceNavigation; 13 | 14 | /** 15 | * 16 | * @author mes 17 | */ 18 | public class UriInfoUtil { 19 | 20 | public static EdmEntitySet getNavigationTargetEntitySet(EdmEntitySet startEdmEntitySet, EdmNavigationProperty edmNavigationProperty) 21 | throws ODataApplicationException { 22 | 23 | EdmEntitySet navigationTargetEntitySet = null; 24 | 25 | String navPropName = edmNavigationProperty.getName(); 26 | EdmBindingTarget edmBindingTarget = startEdmEntitySet.getRelatedBindingTarget(navPropName); 27 | if (edmBindingTarget == null) { 28 | throw new ODataApplicationException("Not supported.", HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ROOT); 29 | } 30 | 31 | if (edmBindingTarget instanceof EdmEntitySet) { 32 | navigationTargetEntitySet = (EdmEntitySet) edmBindingTarget; 33 | } else { 34 | throw new ODataApplicationException("Not supported.", HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ROOT); 35 | } 36 | 37 | return navigationTargetEntitySet; 38 | } 39 | /** 40 | * Find the last navigation segment 41 | * 42 | * @param uriInfo 43 | * @return 44 | */ 45 | public static UriResourceNavigation getLastNavigation(final UriInfoResource uriInfo) { 46 | 47 | final List resourcePaths = uriInfo.getUriResourceParts(); 48 | int navigationCount = 1; 49 | while (navigationCount < resourcePaths.size() 50 | && resourcePaths.get(navigationCount) instanceof UriResourceNavigation) { 51 | navigationCount++; 52 | } 53 | 54 | return (UriResourceNavigation) resourcePaths.get(--navigationCount); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/bloggingit/odata/storage/InMemoryDataStorage.java: -------------------------------------------------------------------------------- 1 | package com.bloggingit.odata.storage; 2 | 3 | import com.bloggingit.odata.model.BaseEntity; 4 | import com.bloggingit.odata.model.Book; 5 | import com.bloggingit.odata.exception.EntityDataException; 6 | import com.bloggingit.odata.model.Author; 7 | import com.bloggingit.odata.model.ContactInfo; 8 | import com.bloggingit.odata.model.Gender; 9 | import com.bloggingit.odata.olingo.v4.util.ReflectionUtils; 10 | import java.time.LocalDateTime; 11 | import java.time.Month; 12 | import java.time.ZoneId; 13 | import java.util.ArrayList; 14 | import java.util.Date; 15 | import java.util.List; 16 | import java.util.Map; 17 | import java.util.concurrent.ConcurrentHashMap; 18 | import java.util.concurrent.ConcurrentMap; 19 | 20 | /** 21 | * This class provides a simple in-memory data storage with some example data. 22 | */ 23 | public class InMemoryDataStorage { 24 | 25 | private static final ConcurrentMap DATA_BOOKS = new ConcurrentHashMap<>(); 26 | private static final ConcurrentMap DATA_AUTHOR = new ConcurrentHashMap<>(); 27 | 28 | static { 29 | createAuthorList(); 30 | createBookList(); 31 | } 32 | private static void createBookList() { 33 | LocalDateTime lDate1 = LocalDateTime.of(2011, Month.JULY, 21, 0, 0); 34 | LocalDateTime lDate2 = LocalDateTime.of(2015, Month.AUGUST, 6, 13, 15); 35 | LocalDateTime lDate3 = LocalDateTime.of(2013, Month.MAY, 12, 0, 0); 36 | 37 | Date date1 = Date.from(lDate1.atZone(ZoneId.systemDefault()).toInstant()); 38 | Date date2 = Date.from(lDate2.atZone(ZoneId.systemDefault()).toInstant()); 39 | Date date3 = Date.from(lDate3.atZone(ZoneId.systemDefault()).toInstant()); 40 | 41 | Book book1 = new Book("Book Title 1", "This is the description of book 1", date1, DATA_AUTHOR.get(1L), 9.95, true); 42 | Book book2 = new Book("Book Title 2", "This is the description of book 2", date2, DATA_AUTHOR.get(2L), 5.99, true); 43 | Book book3 = new Book("Book Title 3", "This is the description of book 3", date3, DATA_AUTHOR.get(3L), 14.50, false); 44 | 45 | book1.setId(1L); 46 | book2.setId(2L); 47 | book3.setId(3L); 48 | 49 | DATA_BOOKS.put(book1.getId(), book1); 50 | DATA_BOOKS.put(book2.getId(), book2); 51 | DATA_BOOKS.put(book3.getId(), book3); 52 | } 53 | 54 | private static void createAuthorList() { 55 | Author author1 = new Author("Author 1", Gender.MALE, new ContactInfo("author1@test.xyz", "123/456")); 56 | Author author2 = new Author("Author 2", Gender.FEMALE, new ContactInfo("author2@test.xyz", "654/321")); 57 | Author author3 = new Author("Author 3", Gender.UNKOWN, new ContactInfo("author3@test.xyz", null)); 58 | 59 | author1.setId(1L); 60 | author2.setId(2L); 61 | author3.setId(3L); 62 | 63 | DATA_AUTHOR.put(author1.getId(), author1); 64 | DATA_AUTHOR.put(author2.getId(), author2); 65 | DATA_AUTHOR.put(author3.getId(), author3); 66 | } 67 | 68 | @SuppressWarnings("unchecked") 69 | private static ConcurrentMap getDataMapByEntityClass(Class entityClazz) { 70 | ConcurrentMap entities = null; 71 | 72 | if (Book.class.equals(entityClazz)) { 73 | entities = (ConcurrentMap) DATA_BOOKS; 74 | } else if (Author.class.equals(entityClazz)) { 75 | entities = (ConcurrentMap) DATA_AUTHOR; 76 | } 77 | 78 | return entities; 79 | } 80 | 81 | public static List getDataListByBaseEntityClass(Class entityClazz) { 82 | final ConcurrentMap entityMap = getDataMapByEntityClass(entityClazz); 83 | 84 | return new ArrayList<>(entityMap.values()); 85 | } 86 | 87 | public static T getDataByClassAndId(Class entityClazz, long id) { 88 | final ConcurrentMap entityMap = getDataMapByEntityClass(entityClazz); 89 | 90 | return entityMap.get(id); 91 | } 92 | 93 | public static void deleteDataByClassAndId(Class entityClazz, long id) { 94 | final ConcurrentMap entityMap = getDataMapByEntityClass(entityClazz); 95 | entityMap.remove(id); 96 | } 97 | 98 | public static T createEntity(T newEntity) throws EntityDataException { 99 | 100 | if (newEntity == null) { 101 | throw new EntityDataException("Unable to create entity, because no entity given"); 102 | } 103 | 104 | if (newEntity instanceof BaseEntity) { 105 | if (newEntity instanceof Book) { 106 | Author author = ((Book) newEntity).getAuthor(); 107 | if (author != null) { 108 | if (author.getId() > 0) { 109 | author = (Author) getDataByClassAndId(newEntity.getClass(), author.getId()); 110 | } else { 111 | author = createEntity(author); 112 | } 113 | ((Book) newEntity).setAuthor(author); 114 | } 115 | } 116 | 117 | @SuppressWarnings("unchecked") 118 | final ConcurrentMap entityMap = getDataMapByEntityClass((Class) newEntity.getClass()); 119 | 120 | BaseEntity baseEntity = (BaseEntity) newEntity; 121 | 122 | if (baseEntity.getId() == 0 || entityMap.putIfAbsent(baseEntity.getId(), newEntity) == null) { 123 | baseEntity.setId(entityMap.size() + 1); 124 | entityMap.put(baseEntity.getId(), newEntity); 125 | } else { 126 | throw new EntityDataException("Could not create entity, because it already exists"); 127 | } 128 | } else { 129 | throw new EntityDataException("Unable to create unsupported entity class" + newEntity); 130 | } 131 | 132 | return newEntity; 133 | } 134 | 135 | @SuppressWarnings("unchecked") 136 | public static T updateEntity(Class entityClazz, long id, Map newPropertyValues, boolean nullableUnkownProperties) throws EntityDataException { 137 | T updatedEntity = null; 138 | 139 | if (BaseEntity.class.isAssignableFrom(entityClazz)) { 140 | final ConcurrentMap entityMap = (ConcurrentMap) getDataMapByEntityClass(entityClazz); 141 | 142 | BaseEntity baseEntity = entityMap.get(id); 143 | 144 | if (baseEntity == null) { 145 | throw new EntityDataException("Unable to update entity, because the entity does not exist"); 146 | } 147 | 148 | newPropertyValues.entrySet().forEach((propEntry) -> { 149 | String fieldname = propEntry.getKey(); 150 | Object value = propEntry.getValue(); 151 | if (!("id".equalsIgnoreCase(fieldname))) { 152 | ReflectionUtils.invokePropertySetter(fieldname, baseEntity, value); 153 | } 154 | }); 155 | 156 | entityMap.put(baseEntity.getId(), baseEntity); 157 | 158 | updatedEntity = (T) baseEntity; 159 | } else { 160 | throw new EntityDataException("Unable to update unsupported entity class" + entityClazz); 161 | } 162 | 163 | return updatedEntity; 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/main/webapp/META-INF/context.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/beans.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | ODataV4-JavaEE-Example-Apache-Olingo 7 | 8 | 9 | 10 | index.html 11 | 12 | -------------------------------------------------------------------------------- /src/main/webapp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ODataV4 - JavaEE - Example - Apache Olingo 5 | 6 | 7 | 8 |

See README file for detailed instructions

9 | 10 | 11 | --------------------------------------------------------------------------------