From 811d2f18190b13f8e5d887c46457fa77cc830be8 Mon Sep 17 00:00:00 2001 From: Onkar Dhuri Date: Wed, 30 Jul 2014 17:23:56 +0530 Subject: [PATCH 1/2] Added new restlet feaures as per odata V3 specifications.- 1. Batch CRUD Support (http://www.odata.org/documentation/odata-version-3-0/batch-processing) 2. MLE/Streaming Functionality (managing media link entities) 3. Better support complex properties 4. Support for Actions & Functions (http://www.odata.org/documentation/odata-version-3-0/atom-format#functions) Also enhanced and fixed - 1. Support HTTP_NEGOTIATE method for form based authentication. 2. Improved command line parsing through Apache commons cli. 3. Improved feed parsing using STAX based model. 4. User friendly parameters for calling functions. 5. primitive -> wrapper class changes to fix null value being sent for empty fields in create/update --- build/tmpl/editions/build.xml | 10 +- libraries/org.core4j_0.5/library.xml | 19 + libraries/org.core4j_0.5/license.txt | 201 ++++ libraries/org.core4j_0.5/org.core4j-0.5.jar | Bin 0 -> 82284 bytes .../org.jvnet.mimepull_1.9.3/library.xml | 19 + .../org.jvnet.mimepull_1.9.3/license.txt | 201 ++++ .../mimepull-1.9.3.jar | Bin 0 -> 62135 bytes ...estlet.engine.security.AuthenticatorHelper | 1 + .../crypto/internal/HttpNegotiateHelper.java | 184 ++++ modules/org.restlet.ext.odata/module.xml | 4 + .../src/org/restlet/ext/odata/Generator.java | 784 +++++++++------- .../src/org/restlet/ext/odata/Query.java | 47 +- .../src/org/restlet/ext/odata/Service.java | 596 +++++++++--- .../odata/batch/request/BatchProperty.java | 52 ++ .../ext/odata/batch/request/BatchRequest.java | 44 + .../odata/batch/request/ChangeSetRequest.java | 53 ++ .../batch/request/ClientBatchRequest.java | 25 + .../batch/request/impl/BatchRequestImpl.java | 219 +++++ .../request/impl/ChangeSetRequestImpl.java | 100 ++ .../request/impl/CreateEntityRequest.java | 80 ++ .../request/impl/DeleteEntityRequest.java | 73 ++ .../batch/request/impl/GetEntityRequest.java | 58 ++ .../request/impl/RestletBatchRequest.java | 165 ++++ .../request/impl/UpdateEntityRequest.java | 86 ++ .../odata/batch/response/BatchResponse.java | 35 + .../batch/response/ChangeSetResponse.java | 22 + .../response/impl/BatchResponseImpl.java | 72 ++ .../response/impl/ChangeSetResponseImpl.java | 66 ++ .../ext/odata/batch/util/BatchConstants.java | 38 + .../ext/odata/batch/util/BodyPart.java | 179 ++++ .../ext/odata/batch/util/HeaderMap.java | 84 ++ .../ext/odata/batch/util/Multipart.java | 72 ++ .../batch/util/RestletBatchRequestHelper.java | 385 ++++++++ .../internal/AtomContentFunctionHandler.java | 74 ++ .../internal/FunctionContentHandler.java | 26 + .../internal/JsonContentFunctionHandler.java | 95 ++ .../ext/odata/internal/edm/EntityType.java | 1 + .../odata/internal/edm/FunctionImport.java | 69 ++ .../ext/odata/internal/edm/Metadata.java | 16 + .../odata/internal/edm/MetadataReader.java | 94 +- .../ext/odata/internal/edm/NamedObject.java | 7 +- .../ext/odata/internal/edm/ODataType.java | 19 +- .../ext/odata/internal/edm/Parameter.java | 17 + .../ext/odata/internal/edm/Property.java | 18 + .../restlet/ext/odata/internal/edm/Type.java | 2 + .../ext/odata/internal/edm/TypeUtils.java | 143 ++- .../odata/internal/reflect/ReflectUtils.java | 32 + .../odata/internal/templates/complexType.ftl | 4 +- .../odata/internal/templates/entityType.ftl | 15 +- .../ext/odata/internal/templates/service.ftl | 198 +++- .../ext/odata/streaming/StreamReference.java | 177 ++++ .../annotation/JavaReservedKeyWord.java | 21 + .../odata/validation/annotation/NotNull.java | 21 + .../validation/annotation/PrimaryKey.java | 21 + .../annotation/SystemGenerated.java | 21 + .../ext/odata/xml/AtomFeedHandler.java | 874 ++++++++++++++++++ .../odata/xml/CollectionPropertyHandler.java | 105 +++ modules/org.restlet.ext.xml/module.xml | 1 + .../restlet/ext/xml/format/FormatParser.java | 28 + .../ext/xml/format/XmlFormatParser.java | 252 +++++ modules/org.restlet.test/module.xml | 1 + .../src/org/restlet/test/batch/crud/Cafe.java | 119 +++ .../test/batch/crud/CafeCrudApplication.java | 346 +++++++ .../restlet/test/batch/crud/CafeService.java | 98 ++ .../test/batch/crud/CreateCafeTestCase.java | 154 +++ .../crud/CreateDeleteChangeSetTestCase.java | 166 ++++ .../crud/CreateUpdateChangeSetTestCase.java | 172 ++++ .../test/batch/crud/DeleteCafeTestCase.java | 152 +++ .../test/batch/crud/GetCafeTestCase.java | 149 +++ .../batch/crud/MultipleChangeSetTestCase.java | 190 ++++ .../MultipleChangeSetWithGetTestCase.java | 186 ++++ .../test/batch/crud/UpdateCafeTestCase.java | 149 +++ .../crud/UpdateDeleteChangeSetTestCase.java | 160 ++++ .../src/org/restlet/test/batch/xml/cafes.xml | 29 + .../test/batch/xml/cafesUpdatedRequest.xml | 29 + .../test/batch/xml/createCafeResponse.xml | 24 + .../test/batch/xml/deleteCafeResponse.xml | 24 + .../test/batch/xml/getCafeResponse.xml | 24 + .../org/restlet/test/batch/xml/metadata.xml | 26 + .../test/batch/xml/parentBatchResponse.xml | 9 + .../test/batch/xml/updateCafeResponse.xml | 27 + .../odata/ODataCafeCustoFeedsTestCase.java | 55 ++ .../test/ext/odata/ODataCafeTestCase.java | 59 ++ .../test/ext/odata/ODataTestSuite.java | 9 + .../org/restlet/test/ext/odata/cafe/Cafe.java | 18 +- .../restlet/test/ext/odata/cafe/Point.java | 153 +++ .../test/ext/odata/cafe/StructAny.java | 156 ++++ .../org/restlet/test/ext/odata/cafe/cafes.xml | 21 + .../restlet/test/ext/odata/cafe/metadata.xml | 15 + .../test/ext/odata/cafecustofeeds/Cafe.java | 20 +- .../test/ext/odata/cafecustofeeds/Point.java | 153 +++ .../ext/odata/cafecustofeeds/StructAny.java | 156 ++++ .../test/ext/odata/cafecustofeeds/cafes.xml | 21 + .../ext/odata/cafecustofeeds/metadata.xml | 15 + .../test/ext/odata/complexcrud/Cafe.java | 158 ++++ .../complexcrud/CafeCrudApplication.java | 87 ++ .../ext/odata/complexcrud/CafeService.java | 44 + .../complexcrud/ODataCafeCrudTestCase.java | 142 +++ .../test/ext/odata/complexcrud/Point.java | 120 +++ .../test/ext/odata/complexcrud/StructAny.java | 124 +++ .../test/ext/odata/complexcrud/cafes.xml | 51 + .../ext/odata/complexcrud/cafesUpdated.xml | 51 + .../test/ext/odata/complexcrud/metadata.xml | 40 + .../org/restlet/test/ext/odata/crud/Cafe.java | 120 +++ .../ext/odata/crud/CafeCrudApplication.java | 84 ++ .../test/ext/odata/crud/CafeService.java | 77 ++ .../ext/odata/crud/ODataCafeCrudTestCase.java | 102 ++ .../org/restlet/test/ext/odata/crud/cafes.xml | 29 + .../test/ext/odata/crud/cafesUpdated.xml | 29 + .../restlet/test/ext/odata/crud/metadata.xml | 24 + .../deepexpand/model/ActivitySector.java | 5 +- .../deepexpand/model/AuthenticatedUser.java | 9 +- .../ext/odata/deepexpand/model/Branch.java | 7 +- .../ext/odata/deepexpand/model/Category.java | 7 +- .../test/ext/odata/deepexpand/model/CoOp.java | 21 +- .../ext/odata/deepexpand/model/Company.java | 11 +- .../odata/deepexpand/model/CompanyPerson.java | 13 +- .../odata/deepexpand/model/Department.java | 5 +- .../ext/odata/deepexpand/model/Division.java | 3 +- .../odata/deepexpand/model/FacultyUser.java | 7 +- .../deepexpand/model/FinancialSource.java | 9 +- .../ext/odata/deepexpand/model/Group.java | 9 +- .../deepexpand/model/InsuranceContract.java | 5 +- .../odata/deepexpand/model/Invitation.java | 3 +- .../test/ext/odata/deepexpand/model/Job.java | 3 +- .../ext/odata/deepexpand/model/JobPart.java | 7 +- .../odata/deepexpand/model/JobPosting.java | 7 +- .../deepexpand/model/JobPostingPart.java | 3 +- .../ext/odata/deepexpand/model/Language.java | 3 +- .../ext/odata/deepexpand/model/Lesson.java | 3 +- .../ext/odata/deepexpand/model/Location.java | 11 +- .../odata/deepexpand/model/Multilingual.java | 3 +- .../odata/deepexpand/model/Nationality.java | 3 +- .../odata/deepexpand/model/Permission.java | 5 +- .../ext/odata/deepexpand/model/Person.java | 7 +- .../ext/odata/deepexpand/model/Professor.java | 23 +- .../odata/deepexpand/model/Registration.java | 17 +- .../ext/odata/deepexpand/model/Report.java | 3 +- .../odata/deepexpand/model/ReportType.java | 5 +- .../odata/deepexpand/model/Requirement.java | 3 +- .../test/ext/odata/deepexpand/model/Role.java | 7 +- .../ext/odata/deepexpand/model/Student.java | 11 +- .../odata/deepexpand/model/University.java | 3 +- .../ext/odata/function/ActionTestCase.java | 66 ++ .../ext/odata/function/FunctionService.java | 85 ++ .../ext/odata/function/FunctionTestCase.java | 59 ++ .../test/ext/odata/function/Nextval_t.java | 39 + .../ext/odata/function/UnitApplication.java | 76 ++ .../ext/odata/function/convertDoubleArray.xml | 2 + .../test/ext/odata/function/metadata.xml | 66 ++ .../test/ext/odata/function/nextval.xml | 2 + .../test/ext/odata/streamcrud/Cafe.java | 178 ++++ .../odata/streamcrud/CafeCrudApplication.java | 124 +++ .../ext/odata/streamcrud/CafeService.java | 44 + .../ODataCafeCrudStreamTestCase.java | 153 +++ .../test/ext/odata/streamcrud/cafes.xml | 29 + .../ext/odata/streamcrud/cafes_Updated.xml | 29 + .../test/ext/odata/streamcrud/metadata.xml | 25 + .../src/org/restlet/data/ChallengeScheme.java | 5 + .../src/org/restlet/data/MediaType.java | 5 + .../engine/header/HeaderConstants.java | 2 + .../restlet/engine/header/HeaderUtils.java | 11 +- .../representation/Representation.java | 40 + .../org/restlet/resource/ClientResource.java | 75 ++ 164 files changed, 11434 insertions(+), 614 deletions(-) create mode 100644 libraries/org.core4j_0.5/library.xml create mode 100644 libraries/org.core4j_0.5/license.txt create mode 100644 libraries/org.core4j_0.5/org.core4j-0.5.jar create mode 100644 libraries/org.jvnet.mimepull_1.9.3/library.xml create mode 100644 libraries/org.jvnet.mimepull_1.9.3/license.txt create mode 100644 libraries/org.jvnet.mimepull_1.9.3/mimepull-1.9.3.jar create mode 100644 modules/org.restlet.ext.crypto/src/org/restlet/ext/crypto/internal/HttpNegotiateHelper.java create mode 100644 modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/request/BatchProperty.java create mode 100644 modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/request/BatchRequest.java create mode 100644 modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/request/ChangeSetRequest.java create mode 100644 modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/request/ClientBatchRequest.java create mode 100644 modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/request/impl/BatchRequestImpl.java create mode 100644 modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/request/impl/ChangeSetRequestImpl.java create mode 100644 modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/request/impl/CreateEntityRequest.java create mode 100644 modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/request/impl/DeleteEntityRequest.java create mode 100644 modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/request/impl/GetEntityRequest.java create mode 100644 modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/request/impl/RestletBatchRequest.java create mode 100644 modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/request/impl/UpdateEntityRequest.java create mode 100644 modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/response/BatchResponse.java create mode 100644 modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/response/ChangeSetResponse.java create mode 100644 modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/response/impl/BatchResponseImpl.java create mode 100644 modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/response/impl/ChangeSetResponseImpl.java create mode 100644 modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/util/BatchConstants.java create mode 100644 modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/util/BodyPart.java create mode 100644 modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/util/HeaderMap.java create mode 100644 modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/util/Multipart.java create mode 100644 modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/util/RestletBatchRequestHelper.java create mode 100644 modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/AtomContentFunctionHandler.java create mode 100644 modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/FunctionContentHandler.java create mode 100644 modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/JsonContentFunctionHandler.java create mode 100644 modules/org.restlet.ext.odata/src/org/restlet/ext/odata/streaming/StreamReference.java create mode 100644 modules/org.restlet.ext.odata/src/org/restlet/ext/odata/validation/annotation/JavaReservedKeyWord.java create mode 100644 modules/org.restlet.ext.odata/src/org/restlet/ext/odata/validation/annotation/NotNull.java create mode 100644 modules/org.restlet.ext.odata/src/org/restlet/ext/odata/validation/annotation/PrimaryKey.java create mode 100644 modules/org.restlet.ext.odata/src/org/restlet/ext/odata/validation/annotation/SystemGenerated.java create mode 100644 modules/org.restlet.ext.odata/src/org/restlet/ext/odata/xml/AtomFeedHandler.java create mode 100644 modules/org.restlet.ext.odata/src/org/restlet/ext/odata/xml/CollectionPropertyHandler.java create mode 100644 modules/org.restlet.ext.xml/src/org/restlet/ext/xml/format/FormatParser.java create mode 100644 modules/org.restlet.ext.xml/src/org/restlet/ext/xml/format/XmlFormatParser.java create mode 100644 modules/org.restlet.test/src/org/restlet/test/batch/crud/Cafe.java create mode 100644 modules/org.restlet.test/src/org/restlet/test/batch/crud/CafeCrudApplication.java create mode 100644 modules/org.restlet.test/src/org/restlet/test/batch/crud/CafeService.java create mode 100644 modules/org.restlet.test/src/org/restlet/test/batch/crud/CreateCafeTestCase.java create mode 100644 modules/org.restlet.test/src/org/restlet/test/batch/crud/CreateDeleteChangeSetTestCase.java create mode 100644 modules/org.restlet.test/src/org/restlet/test/batch/crud/CreateUpdateChangeSetTestCase.java create mode 100644 modules/org.restlet.test/src/org/restlet/test/batch/crud/DeleteCafeTestCase.java create mode 100644 modules/org.restlet.test/src/org/restlet/test/batch/crud/GetCafeTestCase.java create mode 100644 modules/org.restlet.test/src/org/restlet/test/batch/crud/MultipleChangeSetTestCase.java create mode 100644 modules/org.restlet.test/src/org/restlet/test/batch/crud/MultipleChangeSetWithGetTestCase.java create mode 100644 modules/org.restlet.test/src/org/restlet/test/batch/crud/UpdateCafeTestCase.java create mode 100644 modules/org.restlet.test/src/org/restlet/test/batch/crud/UpdateDeleteChangeSetTestCase.java create mode 100644 modules/org.restlet.test/src/org/restlet/test/batch/xml/cafes.xml create mode 100644 modules/org.restlet.test/src/org/restlet/test/batch/xml/cafesUpdatedRequest.xml create mode 100644 modules/org.restlet.test/src/org/restlet/test/batch/xml/createCafeResponse.xml create mode 100644 modules/org.restlet.test/src/org/restlet/test/batch/xml/deleteCafeResponse.xml create mode 100644 modules/org.restlet.test/src/org/restlet/test/batch/xml/getCafeResponse.xml create mode 100644 modules/org.restlet.test/src/org/restlet/test/batch/xml/metadata.xml create mode 100644 modules/org.restlet.test/src/org/restlet/test/batch/xml/parentBatchResponse.xml create mode 100644 modules/org.restlet.test/src/org/restlet/test/batch/xml/updateCafeResponse.xml create mode 100644 modules/org.restlet.test/src/org/restlet/test/ext/odata/cafe/Point.java create mode 100644 modules/org.restlet.test/src/org/restlet/test/ext/odata/cafe/StructAny.java create mode 100644 modules/org.restlet.test/src/org/restlet/test/ext/odata/cafecustofeeds/Point.java create mode 100644 modules/org.restlet.test/src/org/restlet/test/ext/odata/cafecustofeeds/StructAny.java create mode 100644 modules/org.restlet.test/src/org/restlet/test/ext/odata/complexcrud/Cafe.java create mode 100644 modules/org.restlet.test/src/org/restlet/test/ext/odata/complexcrud/CafeCrudApplication.java create mode 100644 modules/org.restlet.test/src/org/restlet/test/ext/odata/complexcrud/CafeService.java create mode 100644 modules/org.restlet.test/src/org/restlet/test/ext/odata/complexcrud/ODataCafeCrudTestCase.java create mode 100644 modules/org.restlet.test/src/org/restlet/test/ext/odata/complexcrud/Point.java create mode 100644 modules/org.restlet.test/src/org/restlet/test/ext/odata/complexcrud/StructAny.java create mode 100644 modules/org.restlet.test/src/org/restlet/test/ext/odata/complexcrud/cafes.xml create mode 100644 modules/org.restlet.test/src/org/restlet/test/ext/odata/complexcrud/cafesUpdated.xml create mode 100644 modules/org.restlet.test/src/org/restlet/test/ext/odata/complexcrud/metadata.xml create mode 100644 modules/org.restlet.test/src/org/restlet/test/ext/odata/crud/Cafe.java create mode 100644 modules/org.restlet.test/src/org/restlet/test/ext/odata/crud/CafeCrudApplication.java create mode 100644 modules/org.restlet.test/src/org/restlet/test/ext/odata/crud/CafeService.java create mode 100644 modules/org.restlet.test/src/org/restlet/test/ext/odata/crud/ODataCafeCrudTestCase.java create mode 100644 modules/org.restlet.test/src/org/restlet/test/ext/odata/crud/cafes.xml create mode 100644 modules/org.restlet.test/src/org/restlet/test/ext/odata/crud/cafesUpdated.xml create mode 100644 modules/org.restlet.test/src/org/restlet/test/ext/odata/crud/metadata.xml create mode 100644 modules/org.restlet.test/src/org/restlet/test/ext/odata/function/ActionTestCase.java create mode 100644 modules/org.restlet.test/src/org/restlet/test/ext/odata/function/FunctionService.java create mode 100644 modules/org.restlet.test/src/org/restlet/test/ext/odata/function/FunctionTestCase.java create mode 100644 modules/org.restlet.test/src/org/restlet/test/ext/odata/function/Nextval_t.java create mode 100644 modules/org.restlet.test/src/org/restlet/test/ext/odata/function/UnitApplication.java create mode 100644 modules/org.restlet.test/src/org/restlet/test/ext/odata/function/convertDoubleArray.xml create mode 100644 modules/org.restlet.test/src/org/restlet/test/ext/odata/function/metadata.xml create mode 100644 modules/org.restlet.test/src/org/restlet/test/ext/odata/function/nextval.xml create mode 100644 modules/org.restlet.test/src/org/restlet/test/ext/odata/streamcrud/Cafe.java create mode 100644 modules/org.restlet.test/src/org/restlet/test/ext/odata/streamcrud/CafeCrudApplication.java create mode 100644 modules/org.restlet.test/src/org/restlet/test/ext/odata/streamcrud/CafeService.java create mode 100644 modules/org.restlet.test/src/org/restlet/test/ext/odata/streamcrud/ODataCafeCrudStreamTestCase.java create mode 100644 modules/org.restlet.test/src/org/restlet/test/ext/odata/streamcrud/cafes.xml create mode 100644 modules/org.restlet.test/src/org/restlet/test/ext/odata/streamcrud/cafes_Updated.xml create mode 100644 modules/org.restlet.test/src/org/restlet/test/ext/odata/streamcrud/metadata.xml diff --git a/build/tmpl/editions/build.xml b/build/tmpl/editions/build.xml index 7b4391ed02..a2806922d4 100644 --- a/build/tmpl/editions/build.xml +++ b/build/tmpl/editions/build.xml @@ -832,9 +832,17 @@ + - + + + + + + + + diff --git a/libraries/org.core4j_0.5/library.xml b/libraries/org.core4j_0.5/library.xml new file mode 100644 index 0000000000..b39897c058 --- /dev/null +++ b/libraries/org.core4j_0.5/library.xml @@ -0,0 +1,19 @@ + + Core 4J + Core 4j Library used by odata4j + 0.5 + 1 + http://code.google.com/p/core4j/source/browse/ + http://mvnrepository.com/artifact/org.core4j/core4j/0.5 + Google Inc. + + + + org.core4j + core4j + + + + + + diff --git a/libraries/org.core4j_0.5/license.txt b/libraries/org.core4j_0.5/license.txt new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/libraries/org.core4j_0.5/license.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/libraries/org.core4j_0.5/org.core4j-0.5.jar b/libraries/org.core4j_0.5/org.core4j-0.5.jar new file mode 100644 index 0000000000000000000000000000000000000000..be8a11d638716f61ec0198a9952a6e6a57fad5cd GIT binary patch literal 82284 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 PEcnR>pUEF50Dt-q83N^N literal 0 HcmV?d00001 diff --git a/libraries/org.jvnet.mimepull_1.9.3/library.xml b/libraries/org.jvnet.mimepull_1.9.3/library.xml new file mode 100644 index 0000000000..7645fbec61 --- /dev/null +++ b/libraries/org.jvnet.mimepull_1.9.3/library.xml @@ -0,0 +1,19 @@ + + MimePull Library + Mimepull Library used by restlet for batch operations + 1.9.3 + 1 + http://mimepull.java.net/ + http://mvnrepository.com/artifact/org.jvnet.mimepull/mimepull/1.9.3 + Oracle Inc. + + + + org.jvnet.mimepull + mimepull + + + + + + diff --git a/libraries/org.jvnet.mimepull_1.9.3/license.txt b/libraries/org.jvnet.mimepull_1.9.3/license.txt new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/libraries/org.jvnet.mimepull_1.9.3/license.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/libraries/org.jvnet.mimepull_1.9.3/mimepull-1.9.3.jar b/libraries/org.jvnet.mimepull_1.9.3/mimepull-1.9.3.jar new file mode 100644 index 0000000000000000000000000000000000000000..c3234556e0dc6a8ee986f524f77a819e75a54ef9 GIT binary patch literal 62135 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 F{|833ENlP( literal 0 HcmV?d00001 diff --git a/modules/org.restlet.ext.crypto/src/META-INF/services/org.restlet.engine.security.AuthenticatorHelper b/modules/org.restlet.ext.crypto/src/META-INF/services/org.restlet.engine.security.AuthenticatorHelper index c3ab215c04..e144058a66 100644 --- a/modules/org.restlet.ext.crypto/src/META-INF/services/org.restlet.engine.security.AuthenticatorHelper +++ b/modules/org.restlet.ext.crypto/src/META-INF/services/org.restlet.engine.security.AuthenticatorHelper @@ -3,3 +3,4 @@ org.restlet.ext.crypto.internal.HttpAwsQueryHelper org.restlet.ext.crypto.internal.HttpDigestHelper org.restlet.ext.crypto.internal.HttpAzureSharedKeyHelper org.restlet.ext.crypto.internal.HttpAzureSharedKeyLiteHelper +org.restlet.ext.crypto.internal.HttpNegotiateHelper \ No newline at end of file diff --git a/modules/org.restlet.ext.crypto/src/org/restlet/ext/crypto/internal/HttpNegotiateHelper.java b/modules/org.restlet.ext.crypto/src/org/restlet/ext/crypto/internal/HttpNegotiateHelper.java new file mode 100644 index 0000000000..2845488432 --- /dev/null +++ b/modules/org.restlet.ext.crypto/src/org/restlet/ext/crypto/internal/HttpNegotiateHelper.java @@ -0,0 +1,184 @@ +/** + * Copyright 2005-2013 Restlet S.A.S. + * + * The contents of this file are subject to the terms of one of the following + * open source licenses: Apache 2.0 or LGPL 3.0 or LGPL 2.1 or CDDL 1.0 or EPL + * 1.0 (the "Licenses"). You can select the license that you prefer but you may + * not use this file except in compliance with one of these Licenses. + * + * You can obtain a copy of the Apache 2.0 license at + * http://www.opensource.org/licenses/apache-2.0 + * + * You can obtain a copy of the LGPL 3.0 license at + * http://www.opensource.org/licenses/lgpl-3.0 + * + * You can obtain a copy of the LGPL 2.1 license at + * http://www.opensource.org/licenses/lgpl-2.1 + * + * You can obtain a copy of the CDDL 1.0 license at + * http://www.opensource.org/licenses/cddl1 + * + * You can obtain a copy of the EPL 1.0 license at + * http://www.opensource.org/licenses/eclipse-1.0 + * + * See the Licenses for the specific language governing permissions and + * limitations under the Licenses. + * + * Alternatively, you can obtain a royalty free commercial license with less + * limitations, transferable or non-transferable, directly at + * http://www.restlet.com/products/restlet-framework + * + * Restlet is a registered trademark of Restlet S.A.S. + */ + +package org.restlet.ext.crypto.internal; + +import java.io.UnsupportedEncodingException; +import java.util.logging.Level; + +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.data.ChallengeResponse; +import org.restlet.data.ChallengeScheme; +import org.restlet.data.CookieSetting; +import org.restlet.data.Form; +import org.restlet.data.Header; +import org.restlet.data.Reference; +import org.restlet.data.Status; +import org.restlet.engine.header.ChallengeWriter; +import org.restlet.engine.header.CookieWriter; +import org.restlet.engine.header.HeaderConstants; +import org.restlet.engine.header.HeaderUtils; +import org.restlet.engine.security.AuthenticatorHelper; +import org.restlet.engine.util.Base64; +import org.restlet.resource.ClientResource; +import org.restlet.util.Series; + +/** + * Implements the HTTP Negotiate helper for form based authentication. It will + * submit the username/password passed as credentials from consumer to default + * action url. Once user is authenticated then we add the cookies sent by server + * to request object. + * + * @author Onkar Dhuri + */ +public class HttpNegotiateHelper extends AuthenticatorHelper { + + /** The action url where form needs to submitted. */ + public static String ACTION_URL = "/j_security_check"; + + /** The field name of username input box. */ + public static String USERNAME = "j_username"; + + /** The field name of password input box. */ + public static String PASSWORD = "j_password"; + + /** + * Constructor. + */ + public HttpNegotiateHelper() { + super(ChallengeScheme.HTTP_NEGOTIATE, true, true); + } + + /** + * This method submits the form to {ACTION_URL} as defined to authenticate + * the request.
+ * Once credentials are validated, the server will send out the 302 Response + * with "JSESSIONID" & "JSESSIONIDSSO" cookies.
+ * We set these cookies into request header for subsequent operations. + * + * @see org.restlet.engine.security.AuthenticatorHelper#formatResponse(org.restlet.engine.header.ChallengeWriter, + * org.restlet.data.ChallengeResponse, org.restlet.Request, + * org.restlet.util.Series) + * + */ + @Override + public void formatResponse(ChallengeWriter cw, ChallengeResponse challenge, + Request request, Series
httpHeaders) { + if (challenge == null) { + throw new RuntimeException( + "No challenge provided, unable to encode credentials"); + } else { + /* + * Submit the form only if we dont have any cookies set in the request. + * For first /$metadata request, it wont have cookies, so it will submit the form and cookies are cached. + * Double check if cached cookies really has JSESSIONID/JSESSIONIDSSO, if not then submit the form again. + */ + if(request.getCookies().size() == 0 || + (request.getCookies().getFirst("JSESSIONID") == null || request.getCookies().getFirst("JSESSIONIDSSO") == null)){ + Reference resourceRef = request.getResourceRef(); + String relativeURL = resourceRef.toString().substring(0, + resourceRef.toString().lastIndexOf("/")); + ClientResource loginCr = new ClientResource(relativeURL + + ACTION_URL); + Form loginForm = new Form(); + loginForm.add(USERNAME, challenge.getIdentifier()); + loginForm.add(PASSWORD, new String(challenge.getSecret())); + loginCr.post(loginForm); + Response response = loginCr.getResponse(); + // check if we get 302 response status; then only add the cookies + // else throw exception + if (response.getStatus().equals(Status.REDIRECTION_FOUND) + || response.getStatus() + .equals(Status.REDIRECTION_PERMANENT)) { + Series cookieSetting = loginCr + .getCookieSettings(); + for (CookieSetting cs : cookieSetting) { + request.getCookies().add(cs.getName(), cs.getValue()); + } + // finally add all the cookies in header + HeaderUtils.addHeader(HeaderConstants.HEADER_COOKIE, + CookieWriter.write(request.getCookies()), httpHeaders); + } else { + throw new RuntimeException( + "Can't authorize the request with passed credentials. " + + "Please check if you are passing correct credentials"); + } + } + } + } + + /** + * For "Negotiate" we are not currently using this API but we might use it + * in future if we need to set the credentials to ChallengeResponse + * + * @see org.restlet.engine.security.AuthenticatorHelper#parseResponse(org.restlet.data.ChallengeResponse, + * org.restlet.Request, org.restlet.util.Series) + * + */ + @Override + public void parseResponse(ChallengeResponse challenge, Request request, + Series
httpHeaders) { + try { + // Check if Negotiate auth header uses Base64 encoding + byte[] credentialsEncoded = Base64.decode(challenge.getRawValue()); + + if (credentialsEncoded == null) { + getLogger() + .info("Cannot decode credentials: " + + challenge.getRawValue()); + } + + String credentials = new String(credentialsEncoded, "ISO-8859-1"); + int separator = credentials.indexOf(':'); + + if (separator == -1) { + // Log the blocking + getLogger().info( + "Invalid credentials given by client with IP: " + + ((request != null) ? request.getClientInfo() + .getAddress() : "?")); + } else { + challenge.setIdentifier(credentials.substring(0, separator)); + challenge.setSecret(credentials.substring(separator + 1)); + } + } catch (UnsupportedEncodingException e) { + getLogger().log(Level.INFO, + "Unsupported HTTP negotiate encoding error", e); + } catch (IllegalArgumentException e) { + getLogger().log(Level.INFO, + "Unable to decode the HTTP Negotiate credential", e); + } + } + +} diff --git a/modules/org.restlet.ext.odata/module.xml b/modules/org.restlet.ext.odata/module.xml index 0ba213f58a..bd33c7ceaa 100644 --- a/modules/org.restlet.ext.odata/module.xml +++ b/modules/org.restlet.ext.odata/module.xml @@ -13,6 +13,10 @@ + + + + diff --git a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/Generator.java b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/Generator.java index 23091fa3ec..2590b2630f 100644 --- a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/Generator.java +++ b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/Generator.java @@ -36,10 +36,20 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; -import java.io.OutputStream; import java.util.HashMap; import java.util.Map; - +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apache.commons.cli.BasicParser; +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.CommandLineParser; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.restlet.data.ChallengeResponse; +import org.restlet.data.ChallengeScheme; import org.restlet.data.CharacterSet; import org.restlet.data.MediaType; import org.restlet.data.Reference; @@ -66,311 +76,467 @@ */ public class Generator { - /** - * Takes two (or three) parameters:
- *
    - *
  1. The URI of the OData service
  2. - *
  3. The output directory (optional, used the current directory by - * default)
  4. - *
  5. The name of the generated service class name (optional)
  6. - *
- * - * @param args - * The list of arguments. - */ - public static void main(String[] args) { - System.out.println("---------------------------"); - System.out.println("OData client code generator"); - System.out.println("---------------------------"); - System.out.println("step 1 - check parameters"); - - String errorMessage = null; - - if (args == null || args.length == 0) { - errorMessage = "Missing mandatory argument: URI of the OData service."; - } - - File outputDir = null; - - if (errorMessage == null) { - if (args.length > 1) { - outputDir = new File(args[1]); - } else { - try { - outputDir = new File(".").getCanonicalFile(); - } catch (IOException e) { - errorMessage = "Unable to get the target directory. " - + e.getMessage(); - } - } - - if (outputDir.exists()) { - System.out.println("step 2 - check the ouput directory"); - if (!outputDir.isDirectory()) { - errorMessage = outputDir.getPath() - + " is not a valid directory."; - } - - } else { - try { - System.out.println("step 2 - create the ouput directory"); - outputDir.mkdirs(); - } catch (Throwable e) { - errorMessage = "Cannot create " + outputDir.getPath() - + " due to: " + e.getMessage(); - } - } - } - if (errorMessage == null) { - System.out.println("step 3 - get the metadata descriptor"); - String dataServiceUri = null; - - if (args[0].endsWith("$metadata")) { - dataServiceUri = args[0].substring(0, args[0].length() - 10); - } else if (args[0].endsWith("/")) { - dataServiceUri = args[0].substring(0, args[0].length() - 1); - } else { - dataServiceUri = args[0]; - } - - Service service = new Service(dataServiceUri); - if (service.getMetadata() == null) { - errorMessage = "Cannot retrieve the metadata."; - } else { - System.out.println("step 4 - generate source code"); - Generator svcUtil = null; - if (args.length == 3) { - svcUtil = new Generator(service.getServiceRef(), args[2]); - } else { - svcUtil = new Generator(service.getServiceRef()); - } - - try { - svcUtil.generate(outputDir); - System.out - .print("The source code has been generated in directory: "); - System.out.println(outputDir.getPath()); - } catch (Exception e) { - errorMessage = "Cannot generate the source code in directory: " - + outputDir.getPath(); - } - } - } - - if (errorMessage != null) { - System.out.println("An error occurred: "); - System.out.println(errorMessage); - System.out.println(); - System.out - .println("Please check that you provide the following parameters:"); - System.out.println(" - Valid URI for the remote service"); - System.out - .println(" - Valid directory path where to generate the files"); - System.out - .println(" - Valid name for the generated service class (optional)"); - } - } - - /** The name of the service class (in case there is only one in the schema). */ - private String serviceClassName; - - /** The URI of the target data service. */ - private Reference serviceRef; - - /** - * Constructor. - * - * @param serviceRef - * The URI of the OData service. - */ - public Generator(Reference serviceRef) { - this(serviceRef, null); - } - - /** - * Constructor. The name of the service class can be provided if there is - * only one service defined in the metadata. - * - * @param serviceRef - * The URI of the OData service. - * @param serviceClassName - * The name of the service class (in case there is only one in - * the metadata). - */ - public Generator(Reference serviceRef, String serviceClassName) { - super(); - this.serviceRef = serviceRef; - if (serviceClassName != null) { - this.serviceClassName = ReflectUtils.normalize(serviceClassName); - this.serviceClassName = this.serviceClassName.substring(0, 1) - .toUpperCase() + this.serviceClassName.substring(1); - } - - } - - /** - * Constructor. - * - * @param serviceUri - * The URI of the OData service. - */ - public Generator(String serviceUri) { - this(serviceUri, null); - } - - /** - * Constructor. The name of the service class can be provided if there is - * only one service defined in the metadata. - * - * @param serviceUri - * The URI of the OData service. - * @param serviceClassName - * The name of the service class (in case there is only one in - * the metadata). - */ - public Generator(String serviceUri, String serviceClassName) { - this(new Reference(serviceUri), serviceClassName); - } - - /** - * Generates the client code to the given output directory. - * - * @param outputDir - * The output directory. - * @throws Exception - */ - public void generate(File outputDir) throws Exception { - Service service = new Service(serviceRef); - Metadata metadata = (Metadata) service.getMetadata(); - if (metadata == null) { - throw new Exception("Can't get the metadata for this service: " - + serviceRef); - } - - Configuration fmc = new Configuration(); - fmc.setDefaultEncoding(CharacterSet.UTF_8.getName()); - - // Generate classes - String rootTemplates = "clap://class/org/restlet/ext/odata/internal/templates"; - Representation complexTmpl = new StringRepresentation( - new ClientResource(rootTemplates + "/complexType.ftl").get() - .getText()); - Representation entityTmpl = new StringRepresentation( - new ClientResource(rootTemplates + "/entityType.ftl").get() - .getText()); - Representation serviceTmpl = new StringRepresentation( - new ClientResource(rootTemplates + "/service.ftl").get() - .getText()); - - for (Schema schema : metadata.getSchemas()) { - if ((schema.getEntityTypes() != null && !schema.getEntityTypes() - .isEmpty()) - || (schema.getComplexTypes() != null && !schema - .getComplexTypes().isEmpty())) { - String packageName = TypeUtils.getPackageName(schema); - File packageDir = new File(outputDir, packageName.replace(".", - System.getProperty("file.separator"))); - packageDir.mkdirs(); - - // For each entity type - for (EntityType type : schema.getEntityTypes()) { - String className = type.getClassName(); - Map dataModel = new HashMap(); - dataModel.put("type", type); - dataModel.put("schema", schema); - dataModel.put("metadata", metadata); - dataModel.put("className", className); - dataModel.put("packageName", packageName); - - TemplateRepresentation templateRepresentation = new TemplateRepresentation( - entityTmpl, fmc, dataModel, MediaType.TEXT_PLAIN); - templateRepresentation.setCharacterSet(CharacterSet.UTF_8); - - // Write the template representation as a Java class - OutputStream fos = new FileOutputStream(new File( - packageDir, type.getClassName() + ".java")); - templateRepresentation.write(fos); - fos.flush(); - } - - for (ComplexType type : schema.getComplexTypes()) { - String className = type.getClassName(); - Map dataModel = new HashMap(); - dataModel.put("type", type); - dataModel.put("schema", schema); - dataModel.put("metadata", metadata); - dataModel.put("className", className); - dataModel.put("packageName", packageName); - - TemplateRepresentation templateRepresentation = new TemplateRepresentation( - complexTmpl, fmc, dataModel, MediaType.TEXT_PLAIN); - templateRepresentation.setCharacterSet(CharacterSet.UTF_8); - - // Write the template representation as a Java class - OutputStream fos = new FileOutputStream(new File( - packageDir, type.getClassName() + ".java")); - templateRepresentation.write(fos); - fos.flush(); - } - } - } - if (metadata.getContainers() != null - && !metadata.getContainers().isEmpty()) { - for (EntityContainer entityContainer : metadata.getContainers()) { - Schema schema = entityContainer.getSchema(); - // Generate Service subclass - StringBuffer className = new StringBuffer(); - - if (serviceClassName != null) { - // Try to use the Client preference - if (entityContainer.isDefaultEntityContainer()) { - className.append(serviceClassName); - } else if (metadata.getContainers().size() == 1) { - className.append(serviceClassName); - } else { - className.append(schema.getNamespace() - .getNormalizedName().substring(0, 1) - .toUpperCase()); - className.append(schema.getNamespace() - .getNormalizedName().substring(1)); - className.append("Service"); - } - } else { - className.append(schema.getNamespace().getNormalizedName() - .substring(0, 1).toUpperCase()); - className.append(schema.getNamespace().getNormalizedName() - .substring(1)); - className.append("Service"); - } - - Map dataModel = new HashMap(); - dataModel.put("schema", schema); - dataModel.put("metadata", metadata); - dataModel.put("className", className); - dataModel.put("dataServiceUri", this.serviceRef.getTargetRef()); - dataModel.put("entityContainer", entityContainer); - - TemplateRepresentation templateRepresentation = new TemplateRepresentation( - serviceTmpl, fmc, dataModel, MediaType.TEXT_PLAIN); - templateRepresentation.setCharacterSet(CharacterSet.UTF_8); - - // Write the template representation as a Java class - OutputStream fos = new FileOutputStream(new File(outputDir, - className + ".java")); - templateRepresentation.write(fos); - fos.flush(); - } - } - } - - /** - * Generates the client code to the given output directory. - * - * @param outputDir - * The output directory. - * @throws Exception - */ - public void generate(String outputDir) throws Exception { - generate(new File(outputDir)); - } + /** The Constant SERVICE_URL. */ + private final static String SERVICE_URL = "serviceUrl"; + + /** The Constant USERNAME. */ + private final static String USERNAME = "userName"; + + /** The Constant PASSWORD. */ + private final static String PASSWORD = "password"; + + /** The Constant CHALLENGE_SCHEME. */ + private final static String CHALLENGE_SCHEME = "challengeScheme"; + + /** The Constant SERVICE_CLASSNAME. */ + private final static String SERVICE_CLASSNAME = "serviceClassName"; + + /** The Constant SERVICE_CLASS_DIR. */ + private final static String SERVICE_CLASS_DIR = "serviceClassDir"; + + /** The Constant ENTITY_CLASS_DIR. */ + private final static String ENTITY_CLASS_DIR = "entityClassDir"; + + /** The URI of the target data service. */ + private Reference serviceUrl; + + /** The user name. */ + private static String userName; + + /** The password. */ + private static String password; + + /** The challenge scheme. */ + private static ChallengeScheme challengeScheme; + + /** The name of the service class (in case there is only one in the schema). */ + private static String serviceClassName; + + /** The entity pkg. */ + private static String entityPkg; + + /** The service pkg. */ + private static String servicePkg; + + /** The package dir where entity classes are generated. */ + private static File packageDir; + + /** The Constant LOGGER. */ + private static final Logger LOGGER = Logger + .getLogger(Generator.class.getName()); + + + /** + * Default Constructor. + * + * @param serviceRef + * The URI of the OData service. + */ + public Generator(Reference serviceRef) { + this(serviceRef, null); + } + + /** + * Constructor. The name of the service class can be provided if there is + * only one service defined in the metadata. + * + * @param serviceRef + * The URI of the OData service. + * @param serviceClassName + * The name of the service class (in case there is only one in + * the metadata). + */ + public Generator(Reference serviceRef, String serviceClassName) { + super(); + this.serviceUrl = serviceRef; + if (serviceClassName != null) { + Generator.serviceClassName = ReflectUtils.normalize(serviceClassName); + Generator.serviceClassName = Generator.serviceClassName.substring(0, 1) + .toUpperCase() + Generator.serviceClassName.substring(1); + } + + } + + /** + * Constructor. + * + * @param serviceUri + * The URI of the OData service. + */ + public Generator(String serviceUri) { + this(serviceUri, null); + } + + /** + * Constructor. The name of the service class can be provided if there is + * only one service defined in the metadata. + * + * @param serviceUri + * The URI of the OData service. + * @param serviceClassName + * The name of the service class (in case there is only one in + * the metadata). + */ + public Generator(String serviceUri, String serviceClassName) { + this(new Reference(serviceUri), serviceClassName); + } + + + /** + * Takes seven parameters:
+ *
    + *
  1. The URI of the OData service
  2. + *
  3. Username to access OData service
  4. + *
  5. Password to access OData service
  6. + *
  7. ChallangeScheme - Possible values HTTP_BASIC, HTTP_NEGOTIATE, if not provided then HTTP_BASIC will be used as default.
  8. + *
  9. Service class name
  10. + *
  11. The output directory for Service class generation (For example : src/com/ow/service), + * if no directory is provided class will be generated in the default package.
  12. + *
  13. The output directory for Entity class generation (For example : src/com/ow/entities), + * if no directory is provided schema name will be used as default package.
  14. + *
+ * + * @param args + * The list of arguments. + */ + public static void main(String[] args) { + + System.out.println("---------------------------"); + System.out.println("OData client code generator"); + System.out.println("---------------------------"); + System.out.println("step 1 - check parameters"); + + String errorMessage = null; + File entityClassDir = null; + File serviceDir = null; + + final CommandLineParser parser = new BasicParser(); + final Options options = new Options(); + setCommandLineOptions(options); + + CommandLine commandLine = null; + // try to parse and validate the passed arguments using apache commons cli. + try { + commandLine = parser.parse(options, args); + } catch (ParseException e) { + System.out.println(e.getMessage()); + printUsage(options); + } + + userName = getOption(USERNAME, commandLine); + password = getOption(PASSWORD, commandLine); + challengeScheme = (getOption(CHALLENGE_SCHEME, commandLine) == null ? null + : ChallengeScheme.valueOf(getOption(CHALLENGE_SCHEME, commandLine))); + + serviceClassName = getOption(SERVICE_CLASSNAME, commandLine); + + if (getOption(SERVICE_CLASS_DIR, commandLine) != null) { + serviceDir = new File(getOption(SERVICE_CLASS_DIR, commandLine)); + servicePkg = getOption(SERVICE_CLASS_DIR, commandLine).substring(4, getOption(SERVICE_CLASS_DIR, commandLine).length()); + } else { + try { + serviceDir = new File(".").getCanonicalFile(); + // set package name to blank if user did not pass this option + servicePkg = ""; + } catch (IOException e) { + errorMessage = "Unable to get the target directory for service generation. " + + e.getMessage(); + } + } + + if (serviceDir.exists()) { + System.out.println("step 2 - check the service directory"); + if (!serviceDir.isDirectory()) { + errorMessage = serviceDir.getPath() + + " is not a valid directory."; + } + + } else { + try { + System.out.println("step 2 - create the service directory"); + serviceDir.mkdirs(); + } catch (Throwable e) { + errorMessage = "Cannot create " + serviceDir.getPath() + + " due to: " + e.getMessage(); + } + } + + if (getOption(ENTITY_CLASS_DIR, commandLine) != null) { + entityClassDir = new File(getOption(ENTITY_CLASS_DIR, commandLine)); + entityPkg = getOption(ENTITY_CLASS_DIR, commandLine).substring(4, getOption(ENTITY_CLASS_DIR, commandLine).length()); + } + + if (errorMessage == null) { + System.out.println("step 3 - get the metadata descriptor"); + String dataServiceUri = getOption(SERVICE_URL, commandLine); + + if (dataServiceUri.endsWith("$metadata")) { + dataServiceUri = dataServiceUri.substring(0, dataServiceUri.length() - 10); + } else if (dataServiceUri.endsWith("/")) { + dataServiceUri = dataServiceUri.substring(0, dataServiceUri.length() - 1); + } + + Service service = new Service(dataServiceUri); + if(challengeScheme != null && userName != null && password != null){ + service.setCredentials(new ChallengeResponse(challengeScheme, userName, + password)); + } + if (service.getMetadata() == null) { + errorMessage = "Cannot retrieve the metadata."; + } else { + System.out.println("step 4 - generate source code"); + Generator svcUtil = new Generator(service.getServiceRef()); + + try { + svcUtil.generate(entityClassDir, serviceDir); + System.out.print("The source code has been generated in directory: "); + System.out.println(packageDir.getPath()); + } catch (Exception e) { + errorMessage = "Cannot generate the source code in directory: " + + packageDir.getPath(); + } + } + } else{ + LOGGER.log(Level.SEVERE,errorMessage); + } + } + + /** + * Sets the command line options. + * + * @param options the new command line options + */ + private static void setCommandLineOptions(Options options) { + + /** Creates an Option for serviceUrl using the specified parameters */ + Option serviceUrlOption = new Option("su", SERVICE_URL, true, "The URI of the OData service"); + serviceUrlOption.setRequired(true); + options.addOption(serviceUrlOption); + + /** Creates an Option for userName */ + Option userNameOption = new Option("ur", USERNAME, true, "Username to access OData service"); + options.addOption(userNameOption); + + /** Creates an Option for password */ + Option passwordOption = new Option("pw", PASSWORD, true, "Password to access OData service"); + options.addOption(passwordOption); + + /** Creates an Option for challangeScheme */ + Option challangeSchemeOption = new Option("cs", CHALLENGE_SCHEME, true, "ChallengeScheme - Possible values HTTP_BASIC, HTTP_NEGOTIATE, if not provided then no authentication will be used"); + options.addOption(challangeSchemeOption); + + /** Creates an Option for sreviceClassName */ + Option sreviceClassNameOption = new Option("sc", SERVICE_CLASSNAME, true, "Service class name"); + options.addOption(sreviceClassNameOption); + + /** Creates an Option for serviceClassDir */ + Option serviceClassDirOption = new Option("sd", SERVICE_CLASS_DIR, true, "The output directory for Service class generation (For example : src/com/edm/entities)"); + options.addOption(serviceClassDirOption); + + /** Creates an Option for entityClassDir */ + Option entityClassDirOption = new Option("ed", ENTITY_CLASS_DIR, true, "The output directory for Entity class generation (For example : src/com/edm/entities"); + options.addOption(entityClassDirOption); + + } + + /** + * Prints the usage. + * + * @param options the options + */ + public static void printUsage(Options options) + { + HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp("available options as follow : ", options ); + System.exit(1); + } + + /** + * To retrieve argument for the Option. + * + * @param option the option + * @param commandLine the command line + * @return the option + */ + public static String getOption(final String option, final CommandLine commandLine) { + + if (commandLine.hasOption(option)) { + return commandLine.getOptionValue(option); + } + + return null; + } + + /** + * Generates the client code to the given output directory. + * + * @param outputDir The output directory. + * @param serviceDir the service dir + * @throws Exception the exception + */ + public void generate(File outputDir, File serviceDir) throws Exception { + Service service = new Service(serviceUrl); + if(challengeScheme != null && userName != null && password != null){ + service.setCredentials(new ChallengeResponse(challengeScheme, userName, + password)); + } + Configuration fmc = new Configuration(); + fmc.setDefaultEncoding(CharacterSet.UTF_8.getName()); + + // Generate classes + String rootTemplates = "clap://class/org/restlet/ext/odata/internal/templates"; + Representation complexTmpl = new StringRepresentation( + new ClientResource(rootTemplates + "/complexType.ftl").get() + .getText()); + Representation entityTmpl = new StringRepresentation( + new ClientResource(rootTemplates + "/entityType.ftl").get() + .getText()); + Representation serviceTmpl = new StringRepresentation( + new ClientResource(rootTemplates + "/service.ftl").get() + .getText()); + + Metadata metadata = (Metadata) service.getMetadata(); + for (Schema schema : metadata.getSchemas()) { + if ((schema.getEntityTypes() != null && !schema.getEntityTypes() + .isEmpty()) + || (schema.getComplexTypes() != null && !schema + .getComplexTypes().isEmpty())) { + + packageDir = outputDir != null ? outputDir : new File( + TypeUtils.getPackageName(schema)); + packageDir.mkdirs(); + + String packageName = outputDir != null ? (entityPkg.replace( + "/", ".")) : TypeUtils.getPackageName(schema); + // For each entity type + for (EntityType type : schema.getEntityTypes()) { + String className = type.getClassName(); + Map dataModel = new HashMap(); + dataModel.put("type", type); + dataModel.put("schema", schema); + dataModel.put("metadata", metadata); + dataModel.put("className", className); + dataModel.put("packageName", packageName); + + try { + TemplateRepresentation templateRepresentation = new TemplateRepresentation( + entityTmpl, fmc, dataModel, + MediaType.TEXT_PLAIN); + templateRepresentation + .setCharacterSet(CharacterSet.UTF_8); + + // Write the template representation as a Java class + templateRepresentation.write(new FileOutputStream( + new File(packageDir, type.getClassName() + + ".java"))); + } catch (Exception e) { + String errormsg = "Exception Occurred in generating entity type for - " + + type.getClassName(); + LOGGER.log(Level.SEVERE,errormsg); + } + + } + + for (ComplexType type : schema.getComplexTypes()) { + String className = type.getClassName(); + Map dataModel = new HashMap(); + dataModel.put("type", type); + dataModel.put("schema", schema); + dataModel.put("metadata", metadata); + dataModel.put("className", className); + dataModel.put("packageName", packageName); + + try { + TemplateRepresentation templateRepresentation = new TemplateRepresentation( + complexTmpl, fmc, dataModel, + MediaType.TEXT_PLAIN); + + templateRepresentation + .setCharacterSet(CharacterSet.UTF_8); + + // Write the template representation as a Java class + templateRepresentation.write(new FileOutputStream( + new File(packageDir, type.getClassName() + + ".java"))); + } catch (Exception e) { + String errormsg = "Exception Occurred in generating complex type for - " + + type.getClassName(); + LOGGER.log(Level.SEVERE,errormsg); + } + } + } + } + + if (metadata.getContainers() != null + && !metadata.getContainers().isEmpty()) { + for (EntityContainer entityContainer : metadata.getContainers()) { + Schema schema = entityContainer.getSchema(); + // Generate Service subclass + StringBuffer className = new StringBuffer(); + if (serviceClassName != null) { + // Try to use the Client preference + if (entityContainer.isDefaultEntityContainer()) { + className.append(serviceClassName); + } else if (metadata.getContainers().size() == 1) { + className.append(serviceClassName); + } else { + className.append(schema.getNamespace() + .getNormalizedName().substring(0, 1) + .toUpperCase()); + className.append(schema.getNamespace() + .getNormalizedName().substring(1)); + className.append("Service"); + } + } else { + className.append(schema.getNamespace().getNormalizedName() + .substring(0, 1).toUpperCase()); + className.append(schema.getNamespace().getNormalizedName() + .substring(1)); + className.append("Service"); + } + + String packageName = outputDir != null ? (servicePkg.replace( + "/", ".")) : TypeUtils.getPackageName(schema); + String entityClassPkg = outputDir != null ? (entityPkg.replace("/", ".")) : TypeUtils + .getPackageName(schema); + Map dataModel = new HashMap(); + dataModel.put("schema", schema); + dataModel.put("metadata", metadata); + dataModel.put("className", className); + dataModel.put(CHALLENGE_SCHEME, challengeScheme != null ? "ChallengeScheme."+challengeScheme.getName() : null); + dataModel.put(USERNAME, userName); + dataModel.put(PASSWORD, password); + dataModel.put("dataServiceUri", service.getServiceRef() + .getTargetRef()); + dataModel.put("entityContainer", entityContainer); + dataModel.put("servicePkg", servicePkg.replace("/", ".")); + dataModel.put("entityClassPkg", entityClassPkg); + dataModel.put("packageName", packageName); + + try { + TemplateRepresentation templateRepresentation = new TemplateRepresentation( + serviceTmpl, fmc, dataModel, MediaType.TEXT_PLAIN); + templateRepresentation.setCharacterSet(CharacterSet.UTF_8); + + // Write the template representation as a Java class + templateRepresentation.write(new FileOutputStream(new File( + serviceDir, className + ".java"))); + } catch (Exception e) { + String errormsg = "Exception Occurred in generating Service class for - " + + className; + LOGGER.log(Level.SEVERE,errormsg); + } + } + } + } + + /** + * Generates the client code to the given output directory. + * + * @param outputDir The output directory. + * @throws Exception the exception + */ + public void generate(String outputDir) throws Exception { + generate(new File(outputDir), new File(outputDir)); + } } diff --git a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/Query.java b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/Query.java index ada2eed62e..89b3dc5ecc 100644 --- a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/Query.java +++ b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/Query.java @@ -51,6 +51,7 @@ import org.restlet.ext.odata.internal.FeedContentHandler; import org.restlet.ext.odata.internal.edm.EntityType; import org.restlet.ext.odata.internal.edm.Metadata; +import org.restlet.ext.odata.xml.AtomFeedHandler; import org.restlet.representation.Representation; import org.restlet.representation.StringRepresentation; import org.restlet.resource.ClientResource; @@ -368,23 +369,15 @@ public void execute() throws Exception { // Guess the type of query based on the URI structure switch (guessType(targetUri)) { case TYPE_ENTITY_SET: - FeedContentHandler feedContentHandler = new FeedContentHandler( - entityClass, entityType, metadata, getLogger()); - setFeed(new Feed(result, feedContentHandler)); - this.count = feedContentHandler.getCount(); - this.entities = feedContentHandler.getEntities(); - break; case TYPE_ENTITY: - EntryContentHandler entryContentHandler = new EntryContentHandler( - entityClass, entityType, metadata, getLogger()); Feed feed = new Feed(); - feed.getEntries().add( - new Entry(result, entryContentHandler)); - setFeed(feed); - entities = new ArrayList(); - if (entryContentHandler.getEntity() != null) { - entities.add(entryContentHandler.getEntity()); - } + AtomFeedHandler feedHandler = new AtomFeedHandler(entityType.getName(), entityType, entityClass, metadata); + ///AtomFeedCursorHandler feedHandler = new AtomFeedCursorHandler(entityType.getName(), entityType, entityClass); + feedHandler.setFeed(feed); + feedHandler.parse(result.getReader()); + this.setFeed(feed); + this.count = -1;// no need to set as we send $count request later + this.entities = feedHandler.getEntities(); break; case TYPE_UNKNOWN: // Guess the type of query based on the returned @@ -394,13 +387,13 @@ public void execute() throws Exception { String string = rep.getText().substring(0, Math.min(100, rep.getText().length())); if (string.contains("( + FeedContentHandler feedContentHandler = new FeedContentHandler( entityClass, entityType, metadata, getLogger()); setFeed(new Feed(rep, feedContentHandler)); this.count = feedContentHandler.getCount(); this.entities = feedContentHandler.getEntities(); } else if (string.contains("( + EntryContentHandler entryContentHandler = new EntryContentHandler( entityClass, entityType, metadata, getLogger()); feed = new Feed(); feed.getEntries().add( @@ -430,6 +423,19 @@ public void execute() throws Exception { } } + + /** + * Gets the query client resource,used in the GetEntityRequest for Batch execution. + * + * @return the query client resource + */ + public ClientResource getQueryClientResource() { + String targetUri = createTargetUri(); + ClientResource resource = service.createResource(new Reference( + targetUri)); + return resource; + } + /** * Creates a new Query with the $expand option set in the URI generated * by the returned query. @@ -777,4 +783,11 @@ public Query skipToken(String token) { public Query top(int rowsCount) { return addParameter("$top", Integer.toString(rowsCount)); } + + /** + * @return + */ + public Class getEntityClass() { + return entityClass; + } } diff --git a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/Service.java b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/Service.java index 3012ae3390..7db4a0ff01 100644 --- a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/Service.java +++ b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/Service.java @@ -35,11 +35,15 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.InputStream; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; @@ -49,6 +53,7 @@ import org.restlet.Response; import org.restlet.data.ChallengeResponse; import org.restlet.data.CharacterSet; +import org.restlet.data.Cookie; import org.restlet.data.Header; import org.restlet.data.MediaType; import org.restlet.data.Parameter; @@ -58,12 +63,10 @@ import org.restlet.data.Tag; import org.restlet.engine.header.HeaderConstants; import org.restlet.engine.header.HeaderReader; +import org.restlet.engine.header.HeaderUtils; import org.restlet.ext.atom.Content; import org.restlet.ext.atom.Entry; -import org.restlet.ext.atom.Feed; -import org.restlet.ext.atom.Link; -import org.restlet.ext.atom.Relation; -import org.restlet.ext.odata.internal.EntryContentHandler; +import org.restlet.ext.odata.batch.util.RestletBatchRequestHelper; import org.restlet.ext.odata.internal.edm.AssociationEnd; import org.restlet.ext.odata.internal.edm.ComplexProperty; import org.restlet.ext.odata.internal.edm.EntityContainer; @@ -73,9 +76,13 @@ import org.restlet.ext.odata.internal.edm.Property; import org.restlet.ext.odata.internal.edm.TypeUtils; import org.restlet.ext.odata.internal.reflect.ReflectUtils; +import org.restlet.ext.odata.streaming.StreamReference; +import org.restlet.ext.odata.validation.annotation.SystemGenerated; +import org.restlet.ext.odata.xml.AtomFeedHandler; import org.restlet.ext.xml.DomRepresentation; import org.restlet.ext.xml.SaxRepresentation; import org.restlet.ext.xml.XmlWriter; +import org.restlet.ext.xml.format.XmlFormatParser; import org.restlet.representation.Representation; import org.restlet.representation.StringRepresentation; import org.restlet.resource.ClientResource; @@ -129,7 +136,13 @@ public class Service { /** The internal logger. */ private Logger logger; - + + private String slug = ""; + + /** + * add Query parameter in request header for each call. + */ + private Map parameters; /** * The maximum version of the OData protocol extensions the client can * accept in a response. @@ -148,6 +161,18 @@ public class Service { /** The reference of the WCF service. */ private Reference serviceRef; + /** The content type. */ + private String contentType; + + /** The isUpdateStreamData used to decide update operation on stream. */ + private boolean isUpdateStreamData; + + /** List of cookies used to cache the cookies sent from server. */ + List cookies; + + /** The isPostRequest used to differentiate POST request. */ + private boolean isPostRequest; + /** * Constructor. * @@ -199,6 +224,8 @@ public Service(String serviceUri) { /** * Adds an entity to an entity set. + * @param + * @param * * @param entitySetName * The path of the entity set relatively to the service URI. @@ -206,41 +233,103 @@ public Service(String serviceUri) { * The entity to put. * @throws Exception */ - public void addEntity(String entitySetName, Object entity) throws Exception { - if (entity != null) { - Entry entry = toEntry(entity); - - ClientResource resource = createResource(entitySetName); - if (getMetadata() == null) { - throw new Exception("Can't add entity to this entity set " - + resource.getReference() - + " due to the lack of the service's metadata."); - } - - try { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - entry.write(baos); - baos.flush(); - StringRepresentation r = new StringRepresentation( - baos.toString(), MediaType.APPLICATION_ATOM); - Representation rep = resource.post(r); - EntryContentHandler entryContentHandler = new EntryContentHandler( - entity.getClass(), (Metadata) getMetadata(), - getLogger()); - Feed feed = new Feed(); - feed.getEntries().add(new Entry(rep, entryContentHandler)); - } catch (ResourceException re) { - throw new ResourceException(re.getStatus(), - "Can't add entity to this entity set " - + resource.getReference()); - } finally { - this.latestRequest = resource.getRequest(); - this.latestResponse = resource.getResponse(); - } - } - } - - /** + public T addEntity(String entitySetName, Object entity) throws Exception { + if (entity != null) { + isPostRequest = Boolean.TRUE; + Metadata metadata = (Metadata) getMetadata(); + EntityType type = metadata.getEntityType(entity.getClass()); + ClientResource resource = createResource(entitySetName); + Representation rep = null; + try { + if (type.isBlob()) { // entity type is set to BLOB if hasStream property of an entity is true. + + InputStream inputStream = this.handleStreamingWithSlug(entity, type); + if (getMetadata() == null) { + throw new Exception("Can't add entity to this entity set " + resource.getReference() + + " due to the lack of the service's metadata."); + } + //post the inputstream with slug header. + rep = resource.post(inputStream, slug, contentType); + + AtomFeedHandler feedHandler = new AtomFeedHandler(type.getName(), type, entity.getClass(), metadata); + T newEntity = RestletBatchRequestHelper.getEntity(rep, feedHandler); + this.merge(entity, feedHandler.getFeed().getEntries().get(0).getId()); //merge the remaining properties using merge request. + return newEntity; + } else { + if (getMetadata() == null) { + throw new Exception("Can't add entity to this entity set " + resource.getReference() + + " due to the lack of the service's metadata."); + } + Entry entry = toEntry(entity); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + entry.write(baos); + baos.flush(); + StringRepresentation r = new StringRepresentation(baos.toString(), MediaType.APPLICATION_ATOM); + rep = resource.post(r); + // parse the response to populate the newly created entity object + + AtomFeedHandler feedHandler = new AtomFeedHandler(type.getName(), type, entity.getClass(), metadata); + T newEntity = RestletBatchRequestHelper.getEntity(rep, feedHandler); + return newEntity; + } + } catch (ResourceException re) { + throw new ResourceException(re.getStatus(), "Can't add entity to this entity set " + + resource.getReference()); + } catch (Exception e) { + getLogger().log(Level.WARNING, + "Can't add the entity : " + " due to " + e.getMessage()); + }finally { + isPostRequest = Boolean.FALSE; + this.latestRequest = resource.getRequest(); + this.latestResponse = resource.getResponse(); + } + } + return null; + } + + + /** + * Method to handle Streaming data for create/update operation. + * It also creates slug header which we need for creating request headers for MLE. + * @param entity + * @param type + * @return + * @throws Exception + */ + private InputStream handleStreamingWithSlug(Object entity, EntityType type) throws Exception { + List properties = type.getProperties(); + Iterator iterator = properties.iterator(); + InputStream inputStream = null; + slug=""; + // Create the SLUG header for Streaming. + // As streaming entity requires mandatory fields to be passed as slug header. + // Request body contains only stream data. Slug header contains mandatory fields like PK. + while (iterator.hasNext()) { + Property prop = iterator.next(); + if (!prop.isNullable()) { + Object propertyObject = ReflectUtils.getPropertyObject(entity, prop.getNormalizedName()); + String propName = prop.getName() + "=" + propertyObject.toString(); + if (slug.isEmpty()) { + slug = propName; + } else { + slug = slug + "," + propName; + } + } else { + if (prop.getType().getName().contains("Stream")) { // find the streaming property from entity and assign stream value to it. + Object propertyObject = ReflectUtils.invokeGetter(entity, prop.getNormalizedName()); + if(null!= propertyObject){ + StreamReference streamReference = (StreamReference) propertyObject; + inputStream = streamReference.getInputStream(); + contentType = streamReference.getContentType(); + isUpdateStreamData = streamReference.isUpdateStreamData(); + } + } + } + } + return inputStream; + } + + /** * Adds an association between the source and the target entity via the * given property name. * @@ -280,7 +369,7 @@ public Query createQuery(String subpath, Class entityClass) { /** * Returns an instance of {@link ClientResource} given an absolute * reference. This resource is completed with the service credentials. This - * method can be overriden in order to complete the sent requests. + * method can be overridden in order to complete the sent requests. * * @param reference * The reference of the target resource. @@ -293,23 +382,41 @@ public ClientResource createResource(Reference reference) { // We provide our own cient connector. resource.setNext(clientConnector); } + + // add the cached cookies to request to prevent submitting the form in form based auth. + if(this.cookies != null){ + resource.getRequest().getCookies().addAll(this.cookies); + } resource.setChallengeResponse(getCredentials()); - + Series
headers = new Series
(Header.class); if (getClientVersion() != null || getMaxClientVersion() != null) { - Series
headers = new Series
(Header.class); - + if (getClientVersion() != null) { headers.add("DataServiceVersion", getClientVersion()); } if (getMaxClientVersion() != null) { headers.add("MaxDataServiceVersion", getMaxClientVersion()); - } - - resource.setAttribute(HeaderConstants.ATTRIBUTE_HEADERS, headers); + } + } - + + /* + * Check if query parameter map is not null and not empty, add all the + * Query parameters in request as a header. + */ + if (getParameter() != null && !getParameter().isEmpty()) { + Iterator> iterator = getParameter() + .entrySet().iterator(); + while (iterator.hasNext()) { + java.util.Map.Entry entry = iterator.next(); + headers.add(entry.getKey(), entry.getValue()); + } + + } + resource.setAttribute(HeaderConstants.ATTRIBUTE_HEADERS, headers); + return resource; } @@ -475,7 +582,7 @@ public String getMaxClientVersion() { * * @return The metadata document related to the current service. */ - protected Object getMetadata() { + public Object getMetadata() { if (metadata == null) { ClientResource resource = createResource("$metadata"); @@ -485,6 +592,14 @@ protected Object getMetadata() { "Get the metadata for " + getServiceRef() + " at " + resource.getReference()); Representation rep = resource.get(MediaType.APPLICATION_XML); + // after metadata request is handled by submitting the form, get the cookies from response and cache it. + Series responseCookies = resource.getResponse().getRequest().getCookies(); + if(responseCookies != null){ + if(this.cookies == null){ + this.cookies = new ArrayList(); + } + this.cookies.addAll(responseCookies); + } this.metadata = new Metadata(rep, resource.getReference()); } catch (ResourceException e) { getLogger().log( @@ -850,18 +965,51 @@ public Representation invokeComplex(String service, if (function != null) { ClientResource resource = createResource(service); - resource.setMethod(function.getMethod()); + if(function.getMethod() !=null){ + resource.setMethod(function.getMethod()); + } if (parameters != null) { - for (org.restlet.ext.odata.internal.edm.Parameter parameter : function - .getParameters()) { - resource.getReference().addQueryParameter( - parameter.getName(), - TypeUtils.getLiteralForm(parameters - .getFirstValue(parameter.getName()), - parameter.getType())); - } + // if this is GET/DELETE method then send the paramenters in query string + if(resource.getMethod().equals(new org.restlet.data.Method("GET")) || resource.getMethod().equals(new org.restlet.data.Method("DELETE"))){ + for (org.restlet.ext.odata.internal.edm.Parameter parameter : function + .getParameters()) { + resource.getReference().addQueryParameter( + parameter.getName(), + TypeUtils.getLiteralForm(parameters + .getFirstValue(parameter.getName()), + parameter.getType())); + } + }else{ + StringBuilder sb = new StringBuilder(); + sb.append("{"+"\n"); + String json =""; + int noOfParameters=0; + for (Parameter parameter : parameters) { + noOfParameters++; + sb.append("\"").append(parameter.getName()).append("\"").append(":"); + for (org.restlet.ext.odata.internal.edm.Parameter edmParameter : function.getParameters()) { + if(parameter.getName().equalsIgnoreCase(edmParameter.getName())){ + if(edmParameter.getType().contains("String")){ + json = "\"" + parameter.getValue()+ "\""; + }else if(edmParameter.getType().contains("Collection")){ + json = parameter.getValue(); + }else{ + json = parameter.getValue(); + } + sb.append(json); + if(noOfParameters!=parameters.size()){ + sb.append(","); + } + } + } + } + sb.append("\n"+"}"); + Series
headerSeries = new Series
(Header.class); + resource.getRequest().setEntity(sb.toString(), MediaType.APPLICATION_JSON); + HeaderUtils.addHeader(HeaderConstants.HEADER_ACCEPT,MediaType.APPLICATION_ATOM.getName(),headerSeries); + resource.setAttribute(HeaderConstants.ATTRIBUTE_HEADERS, headerSeries); + } } - result = resource.handle(); this.latestRequest = resource.getRequest(); this.latestResponse = resource.getResponse(); @@ -1132,6 +1280,8 @@ private void write(XmlWriter writer, Object entity, AttributesImpl nullAttrs) throws SAXException { for (Field field : entity.getClass() .getDeclaredFields()) { + SystemGenerated systemGeneratedAnnotation = field + .getAnnotation(SystemGenerated.class); String getter = "get" + field.getName().substring(0, 1) .toUpperCase() @@ -1139,12 +1289,94 @@ private void write(XmlWriter writer, Object entity, Property prop = ((Metadata) getMetadata()) .getProperty(entity, field.getName()); - if (prop != null) { + if (prop != null && systemGeneratedAnnotation == null && isPostRequest) { + writeProperty(writer, entity, prop, getter, + nullAttrs); + } + else if (prop != null && !isPostRequest) { writeProperty(writer, entity, prop, getter, nullAttrs); } } } + + /** + * Write collection property element. + * + * @param writer the writer + * @param entity the entity + * @param value the value + * @param prop the prop + * @param nullAttrs the null attrs + * @throws SAXException the SAX exception + */ + private void writeCollectionProperty(XmlWriter writer, Object entity, Object value, Property prop, AttributesImpl nullAttrs) throws SAXException { + if (value instanceof List) { + try { + Field field = entity.getClass().getDeclaredField( + prop.getName()); + if (field.getGenericType() instanceof ParameterizedType) { + // determine what type of collection it is + ParameterizedType listType = (ParameterizedType) field + .getGenericType(); + Class listClass = (Class) listType + .getActualTypeArguments()[0]; // get the parameterized class + String mType = null; + boolean isPrimitiveCollection = false; + AttributesImpl typeAttr = new AttributesImpl(); + if(listClass.getName().toLowerCase().startsWith("java")){ // collection of primitives + mType = "Collection("+ TypeUtils.toEdmType(listClass.getName()) + ")"; + isPrimitiveCollection = true; + }else{ // collection of complex + String[] className = listClass.getName().split("\\."); + mType = "Collection("+ className[0].toUpperCase() + "." + className[1] + ")"; + } + List obj = (List) value; + // write collection property tag + typeAttr.addAttribute( + WCF_DATASERVICES_METADATA_NAMESPACE, + "type", "type", "string", mType); + if(obj.size() == 0){ + typeAttr.addAttribute( + WCF_DATASERVICES_METADATA_NAMESPACE, + "null", null, "boolean", "true"); + } + writer.startElement( + WCF_DATASERVICES_NAMESPACE, + prop.getName(), prop.getName(), + typeAttr); + // write element tags + for (Object object : obj) { + if(isPrimitiveCollection){ + if(object.toString().length()>0){ + writer.dataElement(WCF_DATASERVICES_NAMESPACE, XmlFormatParser.DATASERVICES_ELEMENT.getLocalPart(), object.toString()); + }else{ + writer.emptyElement( + WCF_DATASERVICES_NAMESPACE, + XmlFormatParser.DATASERVICES_ELEMENT.getLocalPart(), XmlFormatParser.DATASERVICES_ELEMENT.getLocalPart(), + nullAttrs); + } + }else{ // complex collection + writer.startElement( + WCF_DATASERVICES_NAMESPACE, + XmlFormatParser.DATASERVICES_ELEMENT.getLocalPart()); + // write complex property under + write(writer, object, nullAttrs); + writer.endElement( + WCF_DATASERVICES_NAMESPACE, + XmlFormatParser.DATASERVICES_ELEMENT.getLocalPart()); + } + } + } + } catch (SecurityException e) { + getLogger().warning( + "Can't write the collection property: " + e.getMessage()); + } catch (NoSuchFieldException e) { + getLogger().warning( + "Can't write the collection property: " + e.getMessage()); + } + } + } private void writeProperty(XmlWriter writer, Object entity, Property prop, String getter, @@ -1156,79 +1388,80 @@ private void writeProperty(XmlWriter writer, Object entity, && method.getParameterTypes().length == 0) { Object value = null; - try { - value = method.invoke(entity, - (Object[]) null); - } catch (Exception e) { - } - - if (value != null) { - writer.startElement( - WCF_DATASERVICES_NAMESPACE, - prop.getName()); - - if (prop instanceof ComplexProperty) { - write(writer, value, nullAttrs); - } else { - writer.characters(TypeUtils.toEdm( - value, prop.getType())); - } - - writer.endElement( - WCF_DATASERVICES_NAMESPACE, - prop.getName()); - } else { - if (prop.isNullable()) { - writer.emptyElement( - WCF_DATASERVICES_NAMESPACE, - prop.getName(), prop.getName(), - nullAttrs); - } else { - getLogger().warning( - "The following property has a null value but is not marked as nullable: " - + prop.getName()); - writer.emptyElement( - WCF_DATASERVICES_NAMESPACE, - prop.getName()); - } - } - break; - } + try { + value = method.invoke(entity, + (Object[]) null); + } catch (Exception e) { + getLogger().warning( + "Error occurred while invoking the method : " + e.getMessage()); + } + + if (value != null) { + AttributesImpl typeAttr = new AttributesImpl(); + if (prop instanceof ComplexProperty) { // if this is collection or complex type + if (value instanceof List) { // collection + writeCollectionProperty(writer, + entity, value, prop, nullAttrs); + } else { // complex type + EntityType type = ((Metadata) getMetadata()).getEntityType(entity.getClass()); + // prefix the namespace for m:type + String packageName = type.getSchema().getNamespace().getName() + "." ; + typeAttr.addAttribute( + WCF_DATASERVICES_METADATA_NAMESPACE, + "type", "type", "string",packageName + + ((ComplexProperty) prop) + .getComplexType() + .getName()); + writer.startElement( + WCF_DATASERVICES_NAMESPACE, + prop.getName(), prop.getName(), + typeAttr); + // write data + write(writer, value, nullAttrs); + } + } else { + typeAttr.addAttribute( + WCF_DATASERVICES_METADATA_NAMESPACE, + "type", "type", "string", prop + .getType().getName()); + writer.startElement( + WCF_DATASERVICES_NAMESPACE, + prop.getName(), prop.getName(), + typeAttr); + writer.characters(TypeUtils.toEdm( + value, prop.getType())); + } + + writer.endElement( + WCF_DATASERVICES_NAMESPACE, + prop.getName()); + } else { + if (prop.isNullable()) { + writer.emptyElement( + WCF_DATASERVICES_NAMESPACE, + prop.getName(), prop.getName(), + nullAttrs); + } else { + getLogger().warning( + "The following property has a null value but is not marked as nullable: " + + prop.getName()); + writer.emptyElement( + WCF_DATASERVICES_NAMESPACE, + prop.getName()); + } + } + break; + } } } }; r.setNamespaceAware(true); + result = new Entry(); + Content content = new Content(); + content.setInlineContent(r); + content.setToEncode(false); - if (type.isBlob()) { - result = new Entry() { - @Override - public void writeInlineContent(XmlWriter writer) - throws SAXException { - try { - r.write(writer); - } catch (IOException e) { - throw new SAXException(e); - } - } - }; - result.setNamespaceAware(true); - - Link editLink = new Link(getValueEditRef(entity), - Relation.EDIT_MEDIA, null); - result.getLinks().add(editLink); - Content content = new Content(); - // Get the external blob reference - content.setExternalRef(getValueRef(entity)); - content.setToEncode(false); - result.setContent(content); - } else { - result = new Entry(); - Content content = new Content(); - content.setInlineContent(r); - content.setToEncode(false); - - result.setContent(content); - } + result.setContent(content); } } @@ -1246,9 +1479,17 @@ public void updateEntity(Object entity) throws Exception { if (getMetadata() == null || entity == null) { return; } - - Entry entry = toEntry(entity); - ClientResource resource = createResource(getSubpath(entity)); + EntityType type = metadata.getEntityType(entity.getClass()); + ClientResource resource = createResource(getSubpath(entity)); + if (type.isBlob()) { + InputStream inputStream = this.handleStreamingWithSlug(entity, type); + if (null != inputStream || isUpdateStreamData) {//Check for null inputstream and isUpdateStreamData=true then upadate null to stream data + resource.put(inputStream, slug, contentType); + } + // now do merge request for non-stream properties + this.mergeEntity(entity); + }else{ + Entry entry = toEntry(entity); try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); @@ -1266,10 +1507,103 @@ public void updateEntity(Object entity) throws Exception { } catch (ResourceException re) { throw new ResourceException(re.getStatus(), "Can't update this entity " + resource.getReference()); + } finally { + this.latestRequest = resource.getRequest(); + this.latestResponse = resource.getResponse(); + } + } + } + + /** + * Method for merger operation. + * @param entity + * The entity to merge. + */ + public void mergeEntity(Object entity) { + this.merge(entity,null); + } + + /** + * Updates an entity. + * + * @param entity + * The entity to put. + * @throws Exception + */ + private void merge(Object entity,String id) { + if (getMetadata() == null || entity == null) { + return; + } + EntityType type = metadata.getEntityType(entity.getClass()); + if (type.isBlob()) { + List properties = type.getProperties(); + Iterator iterator = properties.iterator(); + while (iterator.hasNext()) { + Property prop = iterator.next(); + if (prop.getType().getName().contains("Stream")) { + try { + // merge request should not contain data for stream property, so setting it to null. + ReflectUtils.invokeSetter(entity, prop.getNormalizedName(), null); + } catch (Exception e) { + getLogger().warning( + "Can't merge the object: " + e.getMessage()); + } + break; + } + + } + } + Entry entry = toEntry(entity); + + ClientResource resource; + if(null!=id){ + resource = createResource(new Reference(id)); + } + else{ + resource= createResource(getSubpath(entity)); + } + + + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + entry.write(baos); + baos.flush(); + StringRepresentation r = new StringRepresentation(baos.toString(), MediaType.APPLICATION_ATOM); + String tag = getTag(entity); + + if (tag != null) { + // Add a condition + resource.getConditions().setMatch(Arrays.asList(new Tag(tag))); + } + resource.merge(r); + } catch (ResourceException re) { + throw new ResourceException(re.getStatus(), + "Can't update this entity " + resource.getReference()); + } catch (IOException io) { + getLogger().warning( + "IO exception while merging the entity: " + io.getMessage()); } finally { this.latestRequest = resource.getRequest(); this.latestResponse = resource.getResponse(); } } + /** + * Gets Query parameter. + * + * @return the parameter + */ + public Map getParameter() { + return parameters; + } + + /** + * Sets Query parameter in request header. + * + * @param parameter the parameter + */ + public void setParameters(Map parameters) { + this.parameters = parameters; + } + } diff --git a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/request/BatchProperty.java b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/request/BatchProperty.java new file mode 100644 index 0000000000..f32b3c677a --- /dev/null +++ b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/request/BatchProperty.java @@ -0,0 +1,52 @@ +package org.restlet.ext.odata.batch.request; + +import org.restlet.data.Method; +import org.restlet.ext.odata.Service; +import org.restlet.ext.odata.internal.edm.EntityType; + +/** + * The Interface BatchProperty handles the properties of a CRUD requests. + * + * + * copyright 2014 Halliburton + * + * @author Amit.Jahagirdar + */ +public interface BatchProperty extends ClientBatchRequest { + + /** + * Gets the entity class. + * + * @return the entityClass + */ + public Class getEntityClass(); + + /** + * Gets the service. + * + * @return the service + */ + public Service getService(); + + /** + * Gets the entity type. + * + * @return the entity type + */ + public EntityType getEntityType(); + + /** + * Gets the method. + * + * @return the method + */ + public Method getMethod(); + + /** + * Gets the entity set name. + * + * @return the entity set name + */ + public String getEntitySetName(); + +} diff --git a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/request/BatchRequest.java b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/request/BatchRequest.java new file mode 100644 index 0000000000..052257c05a --- /dev/null +++ b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/request/BatchRequest.java @@ -0,0 +1,44 @@ +package org.restlet.ext.odata.batch.request; + +import java.util.List; + +import org.restlet.ext.odata.batch.request.impl.GetEntityRequest; +import org.restlet.ext.odata.batch.response.BatchResponse; + +/** + * The Interface BatchRequest is the base interface for batch.
+ * It has methods to add requests like changeset requests and CRUD requets. + * + * copyright 2014 Halliburton + * + * @author Amit.Jahagirdar + * + */ +public interface BatchRequest { + + /** + * Used for Reading the entities. + * + * @param getEntityRequest + * the get entity request + * @return the batch request impl + */ + public BatchRequest addRequest(GetEntityRequest getEntityRequest); + + /** + * Used for adding changeSetRequest. + * + * @param changeSetRequest + * the change set request + * @return the batch request impl + */ + public BatchRequest addRequest(ChangeSetRequest changeSetRequest); + + /** + * To execute the batch request. + * + * @return the list of batch response + */ + public List execute(); + +} diff --git a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/request/ChangeSetRequest.java b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/request/ChangeSetRequest.java new file mode 100644 index 0000000000..3bbfd63696 --- /dev/null +++ b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/request/ChangeSetRequest.java @@ -0,0 +1,53 @@ +package org.restlet.ext.odata.batch.request; + +import java.util.List; + +import org.restlet.ext.odata.batch.request.impl.CreateEntityRequest; +import org.restlet.ext.odata.batch.request.impl.DeleteEntityRequest; +import org.restlet.ext.odata.batch.request.impl.UpdateEntityRequest; + +/** + * The Interface ChangeSetRequest defines the methods of batch changeset + * requests. + * + * copyright 2014 Halliburton + * + * @author Amit.Jahagirdar + */ +public interface ChangeSetRequest extends ClientBatchRequest { + + /** + * Gets the list of requests. + * + * @return the reqs + */ + public List getReqs(); + + /** + * Adds the request. + * + * @param createEntityRequest + * the create entity request + * @return the change set request + */ + public ChangeSetRequest addRequest(CreateEntityRequest createEntityRequest); + + /** + * Adds the request. + * + * @param updateEntityRequest + * the update entity request + * @return the change set request + */ + public ChangeSetRequest addRequest(UpdateEntityRequest updateEntityRequest); + + /** + * Adds the request. + * + * @param deleteEntityRequest + * the delete entity request + * @return the change set request + */ + public ChangeSetRequest addRequest(DeleteEntityRequest deleteEntityRequest); + +} diff --git a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/request/ClientBatchRequest.java b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/request/ClientBatchRequest.java new file mode 100644 index 0000000000..22c3a4c501 --- /dev/null +++ b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/request/ClientBatchRequest.java @@ -0,0 +1,25 @@ +package org.restlet.ext.odata.batch.request; + +import org.restlet.data.MediaType; + +/** + * The Interface ClientBatchRequest forms the base interface for all types of + * requests like CRUD and changeset requests. + * + * copyright 2014 Halliburton + * + * @author Amit.Jahagirdar + * + */ +public interface ClientBatchRequest { + + /** + * Format. + * + * @param formatType + * the format type + * @return the string + */ + public String format(MediaType formatType); + +} diff --git a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/request/impl/BatchRequestImpl.java b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/request/impl/BatchRequestImpl.java new file mode 100644 index 0000000000..a2f3f3f6ad --- /dev/null +++ b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/request/impl/BatchRequestImpl.java @@ -0,0 +1,219 @@ +package org.restlet.ext.odata.batch.request.impl; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import org.restlet.Client; +import org.restlet.Context; +import org.restlet.data.ClientInfo; +import org.restlet.data.MediaType; +import org.restlet.data.Method; +import org.restlet.data.Protocol; +import org.restlet.data.Reference; +import org.restlet.ext.odata.Service; +import org.restlet.ext.odata.batch.request.BatchRequest; +import org.restlet.ext.odata.batch.request.ChangeSetRequest; +import org.restlet.ext.odata.batch.request.ClientBatchRequest; +import org.restlet.ext.odata.batch.response.BatchResponse; +import org.restlet.ext.odata.batch.util.BatchConstants; +import org.restlet.ext.odata.batch.util.BodyPart; +import org.restlet.ext.odata.batch.util.Multipart; +import org.restlet.ext.odata.batch.util.RestletBatchRequestHelper; +import org.restlet.representation.Representation; +import org.restlet.representation.StringRepresentation; +import org.restlet.resource.ClientResource; + +/** + * The Class BatchRequestImpl forms the base class for batch request.
+ * It maintains the list of clientBatchRequests within a batch. + * + * copyright 2014 Halliburton + * + * @author Amit.Jahagirdar + */ +public class BatchRequestImpl implements BatchRequest { + + /** The service. */ + private Service service; + + /** The requests. */ + private List requests = new ArrayList(); + + /** + * Instantiates a new batch request impl. + * + * @param service + * the service + */ + public BatchRequestImpl(Service service) { + this.service = service; + } + + /* (non-Javadoc) + * @see org.restlet.ext.odata.batch.request.BatchRequest#addRequest(org.restlet.ext.odata.batch.request.impl.GetEntityRequest) + */ + @Override + public BatchRequest addRequest(GetEntityRequest getEntityRequest) { + requests.add(getEntityRequest); + return this; + + } + + /* (non-Javadoc) + * @see org.restlet.ext.odata.batch.request.BatchRequest#addRequest(org.restlet.ext.odata.batch.request.ChangeSetRequest) + */ + @Override + public BatchRequest addRequest(ChangeSetRequest changeSetRequest) { + requests.add(changeSetRequest); + return this; + } + + /* (non-Javadoc) + * @see org.restlet.ext.odata.batch.request.BatchRequest#execute() + */ + @Override + public List execute() { + + String batchId = generateBatchId(); + + ClientResource clientResource = service.createResource(new Reference( + service.getServiceRef())); + Reference resourceRef = clientResource.getRequest().getResourceRef(); + + // create the client Info + setClientContext(clientResource, resourceRef); + + StringBuilder sb = createBatchString(batchId, this.requests); + //Finally posting the batch request. + Representation r = clientResource.post(new StringRepresentation(sb + .toString(), new MediaType(MediaType.MULTIPART_MIXED + + ";boundary=" + batchId))); + + List batchResponses = null; + try { + batchResponses = parseRepresentation(r, this.requests); + } catch (IOException e) { + throw new RuntimeException(e); + } + return batchResponses; + } + + /** + * Creates the batch request string,which would then be converted into String Representation for further processing. + * + * @param batchId + * @param list + * @return + */ + private StringBuilder createBatchString(String batchId, + List list) { + StringBuilder sb = new StringBuilder(); + + + for (ClientBatchRequest restletBatchRequest : list) { + if (restletBatchRequest instanceof GetEntityRequest) { + sb.append(BatchConstants.NEW_LINE_BATCH_START).append(batchId).append(BatchConstants.NEW_LINE); + sb.append(restletBatchRequest.format(MediaType.APPLICATION_ATOM)); + } else if (restletBatchRequest instanceof ChangeSetRequest) { + sb.append(BatchConstants.NEW_LINE_BATCH_START).append(batchId).append(BatchConstants.NEW_LINE); + sb.append(restletBatchRequest.format(MediaType.APPLICATION_ATOM)); + } + } + sb.append(BatchConstants.NEW_LINE_BATCH_START).append(batchId).append(BatchConstants.NEW_LINE_BATCH_END); + return sb; + } + + /** + * Sets the client information and the context onto client resource. + * @param clientResource + * @param resourceRef + */ + private void setClientContext(ClientResource clientResource, + Reference resourceRef) { + ClientInfo clientInfo = new ClientInfo(); + clientResource.getRequest().setClientInfo(clientInfo); + + Context context = new Context(); + Client client = new Client(context, Protocol.HTTP); + client.getContext().getParameters() + .add("useForwardedForHeader", "false"); + + + clientResource.getRequest().setResourceRef( + new Reference(resourceRef.getTargetRef() + + BatchConstants.BATCH_ENDPOINT_URI)); + clientResource.getRequest().setMethod(Method.POST); + clientResource.setNext(client); + } + + /** + * Generates a unique batch Id for each batch request. + * @return + */ + private String generateBatchId() { + String batchId = BatchConstants.BATCH_UNDERSCORE + + UUID.randomUUID().toString(); + return batchId; + } + + /** + * This method parses the representation and returns the list of batch + * responses. + * + * @param r + * representation + * @param list + * the list + * @return list of batchResponses. + * @throws IOException + */ + private List parseRepresentation(Representation r, + List list) throws IOException { + + // This would hold individual batch responses + List batchResultList = new ArrayList( + list.size()); + + MediaType mediaType = r.getMediaType(); + Multipart baseMultiPart = RestletBatchRequestHelper.createMultipart( + r.getStream(), mediaType); + int i = 0; + BatchResponse bResponse = null; + List subBodyParts = baseMultiPart.getBodyParts(); + for (BodyPart bp : subBodyParts) { + // Its a changeset + if (bp.getMediaType().isCompatible(MediaType.MULTIPART_MIXED)) { + Multipart mp = RestletBatchRequestHelper.createMultipart( + bp.getInputStream(), bp.getMediaType()); + List contentList = new ArrayList(); + List bodyParts = mp.getBodyParts(); + for (BodyPart bodyPart : bodyParts) { + contentList + .add(RestletBatchRequestHelper + .getStringFromInputStream(bodyPart + .getInputStream())); + } + ChangeSetRequest csr = (ChangeSetRequest) list.get(i); + bResponse = RestletBatchRequestHelper.parseChangeSetResponse( + BatchConstants.ODATA_VERSION_V3, contentList, csr, + mediaType, service); + } else { + ClientBatchRequest batchRequestOfTypeGet = list.get(i); + String content = RestletBatchRequestHelper + .getStringFromInputStream(bp.getInputStream()); + bResponse = RestletBatchRequestHelper + .parseSingleOperationResponse( + BatchConstants.ODATA_VERSION_V3, content, + batchRequestOfTypeGet, bp.getMediaType(), + service); + } + batchResultList.add(bResponse); + i++; + } + return batchResultList; + + } + +} diff --git a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/request/impl/ChangeSetRequestImpl.java b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/request/impl/ChangeSetRequestImpl.java new file mode 100644 index 0000000000..c855171058 --- /dev/null +++ b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/request/impl/ChangeSetRequestImpl.java @@ -0,0 +1,100 @@ +package org.restlet.ext.odata.batch.request.impl; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import org.restlet.data.MediaType; +import org.restlet.engine.header.HeaderConstants; +import org.restlet.ext.odata.batch.request.ChangeSetRequest; +import org.restlet.ext.odata.batch.request.ClientBatchRequest; +import org.restlet.ext.odata.batch.util.BatchConstants; + + +/** + * The Class ChangeSetRequestImpl. + * + * + * copyright 2014 Halliburton + * + * @author Amit.Jahagirdar + */ +public class ChangeSetRequestImpl implements ChangeSetRequest { + + /** The reqs. */ + private List reqs = new ArrayList(); + + + /* (non-Javadoc) + * @see org.restlet.ext.odata.batch.request.ChangeSetRequest#addRequest(org.restlet.ext.odata.batch.request.impl.CreateEntityRequest) + */ + @Override + public ChangeSetRequest addRequest(CreateEntityRequest createEntityRequest) { + reqs.add(createEntityRequest); + return this; + } + + + /* (non-Javadoc) + * @see org.restlet.ext.odata.batch.request.ChangeSetRequest#getReqs() + */ + @Override + public List getReqs() { + return reqs; + } + + + /* (non-Javadoc) + * @see org.restlet.ext.odata.batch.request.ClientBatchRequest#format(org.restlet.data.MediaType) + */ + @Override + public String format(MediaType formatType) { + + StringBuilder sb = new StringBuilder(); + // nothing to add + if (reqs == null || reqs.size() == 0) { + return ""; + } + + String boundary = BatchConstants.CHANGESET_UNDERSCORE + + UUID.randomUUID().toString(); + String cType = MediaType.MULTIPART_MIXED + "; " + + BatchConstants.BATCH_BOUNDARY + "=" + boundary; + sb.append(HeaderConstants.HEADER_CONTENT_TYPE).append(": ") + .append(cType).append(BatchConstants.NEW_LINE); + sb.append(BatchConstants.NEW_LINE); + + + for (ClientBatchRequest req : reqs) { + sb.append(BatchConstants.NEW_LINE_BATCH_START).append(boundary).append(BatchConstants.NEW_LINE); + sb.append(req.format(formatType)); + } + + // ending the change set + sb.append(BatchConstants.NEW_LINE_BATCH_START).append(boundary).append(BatchConstants.NEW_LINE_BATCH_END); + + return sb.toString(); + + } + + + /* (non-Javadoc) + * @see org.restlet.ext.odata.batch.request.ChangeSetRequest#addRequest(org.restlet.ext.odata.batch.request.impl.UpdateEntityRequest) + */ + @Override + public ChangeSetRequest addRequest(UpdateEntityRequest updateEntityRequest) { + reqs.add(updateEntityRequest); + return this; + } + + + /* (non-Javadoc) + * @see org.restlet.ext.odata.batch.request.ChangeSetRequest#addRequest(org.restlet.ext.odata.batch.request.impl.DeleteEntityRequest) + */ + @Override + public ChangeSetRequest addRequest(DeleteEntityRequest deleteEntityRequest) { + reqs.add(deleteEntityRequest); + return this; + } + +} diff --git a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/request/impl/CreateEntityRequest.java b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/request/impl/CreateEntityRequest.java new file mode 100644 index 0000000000..a8a7ddb1a4 --- /dev/null +++ b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/request/impl/CreateEntityRequest.java @@ -0,0 +1,80 @@ +package org.restlet.ext.odata.batch.request.impl; + +import org.restlet.data.MediaType; +import org.restlet.data.Method; +import org.restlet.engine.header.HeaderConstants; +import org.restlet.ext.atom.Entry; +import org.restlet.ext.odata.Service; +import org.restlet.ext.odata.batch.util.BatchConstants; +import org.restlet.ext.odata.batch.util.RestletBatchRequestHelper; +import org.restlet.representation.StringRepresentation; +import org.restlet.resource.ClientResource; + +/** + * The Class CreateEntityRequest is used with POST method to create an entity + * using batch. + * + * + * copyright 2014 Halliburton + * + * @author Amit.Jahagirdar + */ +public class CreateEntityRequest extends RestletBatchRequest { + + /** The entry. */ + private Entry entry; + + /** + * Instantiates a new creates the entity request. + * + * @param service + * the service + * @param entity + * the entity + * @throws Exception + * the exception + */ + public CreateEntityRequest(Service service, Object entity) throws Exception { + super(service, (Class) entity.getClass(), Method.POST); + this.entry = service.toEntry(entity); + } + + /* (non-Javadoc) + * @see org.restlet.ext.odata.batch.request.ClientBatchRequest#format(org.restlet.data.MediaType) + */ + @Override + public String format(MediaType formatType) { + ClientResource cr = getClientResource(this.getEntitySetName()); + StringRepresentation strRepresent = RestletBatchRequestHelper + .getStringRepresentation(this.getService(), + this.getEntitySetName(), this.entry,formatType); + StringBuilder sb = new StringBuilder(); + sb.append(RestletBatchRequestHelper.formatSingleRequest( + cr.getRequest(), formatType)); + // set content-length + sb.append(HeaderConstants.HEADER_CONTENT_LENGTH).append(": ") + .append(strRepresent.getSize()).append(BatchConstants.NEW_LINE); + sb.append(BatchConstants.NEW_LINE).append(BatchConstants.NEW_LINE); + sb.append(strRepresent.getText()).append(BatchConstants.NEW_LINE); + return sb.toString(); + } + + /** + * Gets the entry. + * + * @return the entry + */ + public Entry getEntry() { + return entry; + } + + /** + * Sets the entry. + * + * @param entry + * the new entry + */ + public void setEntry(Entry entry) { + this.entry = entry; + } +} diff --git a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/request/impl/DeleteEntityRequest.java b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/request/impl/DeleteEntityRequest.java new file mode 100644 index 0000000000..3bee4c759a --- /dev/null +++ b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/request/impl/DeleteEntityRequest.java @@ -0,0 +1,73 @@ +package org.restlet.ext.odata.batch.request.impl; + +import org.restlet.data.MediaType; +import org.restlet.data.Method; +import org.restlet.ext.atom.Entry; +import org.restlet.ext.odata.Service; +import org.restlet.ext.odata.batch.util.RestletBatchRequestHelper; +import org.restlet.resource.ClientResource; + +/** + * The Class DeleteEntityRequest is used with DELETE method to delete an entity + * using batch.. + * + * + * copyright 2014 Halliburton + * + * @author Amit.Jahagirdar + */ +public class DeleteEntityRequest extends RestletBatchRequest { + + /** The entry. */ + private Entry entry; + + /** The entity sub path. */ + private String entitySubPath; + + /** + * Instantiates a new delete entity request. + * + * @param service + * the service + * @param entity + * the entity + * @throws Exception + * the exception + */ + public DeleteEntityRequest(Service service, Object entity) throws Exception { + super(service, entity.getClass(), Method.DELETE); + this.entry = service.toEntry(entity); + this.entitySubPath = RestletBatchRequestHelper.getEntitySubPath( + service, entity); + + } + + /* (non-Javadoc) + * @see org.restlet.ext.odata.batch.request.ClientBatchRequest#format(org.restlet.data.MediaType) + */ + @Override + public String format(MediaType formatType) { + ClientResource cr = getClientResource(this.entitySubPath); + return RestletBatchRequestHelper.formatSingleRequest(cr.getRequest(), + formatType); + } + + /** + * Gets the entry. + * + * @return the entry + */ + public Entry getEntry() { + return entry; + } + + /** + * Sets the entry. + * + * @param entry + * the new entry + */ + public void setEntry(Entry entry) { + this.entry = entry; + } +} diff --git a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/request/impl/GetEntityRequest.java b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/request/impl/GetEntityRequest.java new file mode 100644 index 0000000000..a7ffa37a7d --- /dev/null +++ b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/request/impl/GetEntityRequest.java @@ -0,0 +1,58 @@ +package org.restlet.ext.odata.batch.request.impl; + +import org.restlet.data.MediaType; +import org.restlet.data.Method; +import org.restlet.ext.odata.Query; +import org.restlet.ext.odata.batch.util.RestletBatchRequestHelper; +import org.restlet.resource.ClientResource; + +/** + * The Class GetEntityRequest is specifically used within a batch request,to + * fetch the entity. + * + * + * copyright 2014 Halliburton + * + * @author Amit.Jahagirdar + */ +public class GetEntityRequest extends RestletBatchRequest { + + /** The query. */ + private Query query; + + /** + * Instantiates a new gets the entity request. + * + * @param service + * the service + * @param query + * the query + * @throws Exception + */ + public GetEntityRequest(Query query) throws Exception { + super(query.getService(), query.getEntityClass(), Method.GET); + this.query = query; + } + + /** + * Gets the query. + * + * @return the query + */ + public Query getQuery() { + return query; + } + + + /* (non-Javadoc) + * @see org.restlet.ext.odata.batch.request.ClientBatchRequest#format(org.restlet.data.MediaType) + */ + @Override + public String format(MediaType formatType) { + ClientResource cr = getClientResource(this.query.getSubpath()); + return RestletBatchRequestHelper.formatSingleRequest(cr.getRequest(), + formatType); + } + + +} diff --git a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/request/impl/RestletBatchRequest.java b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/request/impl/RestletBatchRequest.java new file mode 100644 index 0000000000..32e20d1411 --- /dev/null +++ b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/request/impl/RestletBatchRequest.java @@ -0,0 +1,165 @@ +package org.restlet.ext.odata.batch.request.impl; + +import org.restlet.data.Method; +import org.restlet.ext.odata.Service; +import org.restlet.ext.odata.batch.request.BatchProperty; +import org.restlet.ext.odata.batch.util.RestletBatchRequestHelper; +import org.restlet.ext.odata.internal.edm.EntityType; +import org.restlet.ext.odata.internal.edm.Metadata; +import org.restlet.resource.ClientResource; + + +/** + * Abstract implementation of the RestletBatchRequest interface to handle the + * mechanism for inferring the entity type from the entity class using the + * service, for CRUD requests.
+ * + * + * copyright 2014 Halliburton + * + * @author Amit.Jahagirdar + */ +public abstract class RestletBatchRequest implements BatchProperty { + + /** The service. */ + private Service service; + + /** The entity class. */ + private Class entityClass; + + /** The entity type. */ + private EntityType entityType; + + /** The method. */ + private Method method; + + /** The entity set name. */ + private String entitySetName; + + /** + * Instantiates a new restlet batch request impl. + * + * @param service the service + * @param entityClass the entity class + * @param method the method + * @throws Exception the exception + */ + public RestletBatchRequest(Service service, Class entityClass, + Method method) throws Exception { + this.setService(service); + this.setEntityClass(entityClass); + this.entityType = inferEntityType(entityClass); + this.method = method; + this.setEntitySetName(RestletBatchRequestHelper + .validateAndReturnEntitySetName(service, entityClass)); + } + + /** + * Method to get the entity type from the entity class. + * + * @param entityClass + * the entity class + * @return the entity type + */ + private EntityType inferEntityType(Class entityClass) { + Metadata metadata = (Metadata) service.getMetadata(); + return metadata.getEntityType(entityClass); + } + + /** + * Gets the client resource. + * + * @param relativePath the relative path + * @return the client resource + */ + public ClientResource getClientResource(String relativePath) { + ClientResource cr = this.getService().createResource(relativePath); + cr.getRequest().setMethod(this.getMethod()); + return cr; + } + + /** + * Gets the entity class. + * + * @return the entityClass + */ + public Class getEntityClass() { + return entityClass; + } + + /** + * Sets the entity class. + * + * @param entityClass + * the entityClass to set + */ + public void setEntityClass(Class entityClass) { + this.entityClass = entityClass; + } + + /** + * Gets the service. + * + * @return the service + */ + public Service getService() { + return service; + } + + /** + * Sets the service. + * + * @param service + * the service to set + */ + public void setService(Service service) { + this.service = service; + } + + + /* (non-Javadoc) + * @see org.restlet.ext.odata.batch.request.BatchProperty#getEntityType() + */ + @Override + public EntityType getEntityType() { + return entityType; + } + + + /* (non-Javadoc) + * @see org.restlet.ext.odata.batch.request.BatchProperty#getMethod() + */ + @Override + public Method getMethod() { + return method; + } + + /** + * Sets the method. + * + * @param method + * the new method + */ + protected void setMethod(Method method) { + this.method = method; + } + + + /* (non-Javadoc) + * @see org.restlet.ext.odata.batch.request.BatchProperty#getEntitySetName() + */ + @Override + public String getEntitySetName() { + return entitySetName; + } + + /** + * Sets the entity set name. + * + * @param entitySetName + * the new entity set name + */ + public void setEntitySetName(String entitySetName) { + this.entitySetName = entitySetName; + } +} diff --git a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/request/impl/UpdateEntityRequest.java b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/request/impl/UpdateEntityRequest.java new file mode 100644 index 0000000000..507641896e --- /dev/null +++ b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/request/impl/UpdateEntityRequest.java @@ -0,0 +1,86 @@ +package org.restlet.ext.odata.batch.request.impl; + +import org.restlet.data.MediaType; +import org.restlet.data.Method; +import org.restlet.engine.header.HeaderConstants; +import org.restlet.ext.atom.Entry; +import org.restlet.ext.odata.Service; +import org.restlet.ext.odata.batch.util.BatchConstants; +import org.restlet.ext.odata.batch.util.RestletBatchRequestHelper; +import org.restlet.representation.StringRepresentation; +import org.restlet.resource.ClientResource; + +/** + * The Class UpdateEntityRequest is used with PUT method to update an entity + * using batch. + * + * copyright 2014 Halliburton + * + * @author Amit.Jahagirdar + */ +public class UpdateEntityRequest extends RestletBatchRequest { + + /** The entry. */ + private Entry entry; + + /** The entity sub path. */ + private String entitySubPath; + + /** + * Instantiates a new update entity request. + * + * @param service + * the service + * @param entity + * the entity + * @throws Exception + * the exception + */ + public UpdateEntityRequest(Service service, Object entity) throws Exception { + super(service, entity.getClass(), Method.PUT); + + this.entry = service.toEntry(entity); + this.entitySubPath = RestletBatchRequestHelper.getEntitySubPath( + service, entity); + } + + + /* (non-Javadoc) + * @see org.restlet.ext.odata.batch.request.ClientBatchRequest#format(org.restlet.data.MediaType) + */ + @Override + public String format(MediaType formatType) { + ClientResource cr = getClientResource(this.entitySubPath); + StringRepresentation strRepresent = RestletBatchRequestHelper + .getStringRepresentation(this.getService(), + this.getEntitySetName(), this.entry,formatType); + StringBuilder sb = new StringBuilder(); + sb.append(RestletBatchRequestHelper.formatSingleRequest( + cr.getRequest(), formatType)); + // set content-length + sb.append(HeaderConstants.HEADER_CONTENT_LENGTH).append(": ") + .append(strRepresent.getSize()).append(BatchConstants.NEW_LINE); + sb.append(BatchConstants.NEW_LINE).append(BatchConstants.NEW_LINE); + sb.append(strRepresent.getText()).append(BatchConstants.NEW_LINE); + return sb.toString(); + } + + /** + * Gets the entry. + * + * @return the entry + */ + public Entry getEntry() { + return entry; + } + + /** + * Sets the entry. + * + * @param entry + * the new entry + */ + public void setEntry(Entry entry) { + this.entry = entry; + } +} diff --git a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/response/BatchResponse.java b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/response/BatchResponse.java new file mode 100644 index 0000000000..d54487ea53 --- /dev/null +++ b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/response/BatchResponse.java @@ -0,0 +1,35 @@ +package org.restlet.ext.odata.batch.response; + +import javax.ws.rs.core.MultivaluedMap; + +/** + * The Interface BatchResponse is the top level response interface for a batch. + * + * copyright 2014 Halliburton + * + * @author Amit.Jahagirdar + */ +public interface BatchResponse { + + /** + * Gets the entity. + * + * @return the entity + */ + Object getEntity(); + + /** + * Gets the status. + * + * @return the status + */ + int getStatus(); + + /** + * Gets the headers. + * + * @return the headers + */ + MultivaluedMap getHeaders(); + +} diff --git a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/response/ChangeSetResponse.java b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/response/ChangeSetResponse.java new file mode 100644 index 0000000000..9970fa67d3 --- /dev/null +++ b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/response/ChangeSetResponse.java @@ -0,0 +1,22 @@ +package org.restlet.ext.odata.batch.response; + +/** + * The Interface ChangeSetResponse exposes methods to hold the CUD responses + * within a batch response. + * + * + * copyright 2014 Halliburton + * + * @author Amit.Jahagirdar + */ +public interface ChangeSetResponse extends BatchResponse { + + /** + * Adds the. + * + * @param singleResponse + * the single response + */ + void add(BatchResponse singleResponse); + +} diff --git a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/response/impl/BatchResponseImpl.java b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/response/impl/BatchResponseImpl.java new file mode 100644 index 0000000000..73b857fafe --- /dev/null +++ b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/response/impl/BatchResponseImpl.java @@ -0,0 +1,72 @@ +package org.restlet.ext.odata.batch.response.impl; + +import javax.ws.rs.core.MultivaluedMap; + +import org.restlet.ext.odata.batch.response.BatchResponse; + +/** + * The Class BatchResponseImpl is the implementation class for hold the state of + * the response.
+ * It contains properties like status returned by the http + * response,headers,object entity in case of Create and Get entity requests. + * + * copyright 2014 Halliburton + * + * @author Amit.Jahagirdar + */ +public class BatchResponseImpl implements BatchResponse { + + /** The status. */ + private int status; + + /** The headers. */ + final MultivaluedMap headers; + + /** The entity. */ + private Object entity; + + /** + * Instantiates a new batch response impl. + * + * @param statusCode + * the status code + * @param batchHeaders + * the batch headers + * @param entity + * the entity + */ + public BatchResponseImpl(int statusCode, + MultivaluedMap batchHeaders, Object entity) { + this.entity = entity; + this.status = statusCode; + this.headers = batchHeaders; + } + + + /* (non-Javadoc) + * @see org.restlet.ext.odata.batch.response.BatchResponse#getEntity() + */ + @Override + public Object getEntity() { + return entity; + } + + + /* (non-Javadoc) + * @see org.restlet.ext.odata.batch.response.BatchResponse#getStatus() + */ + @Override + public int getStatus() { + return status; + } + + + /* (non-Javadoc) + * @see org.restlet.ext.odata.batch.response.BatchResponse#getHeaders() + */ + @Override + public MultivaluedMap getHeaders() { + return headers; + } + +} diff --git a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/response/impl/ChangeSetResponseImpl.java b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/response/impl/ChangeSetResponseImpl.java new file mode 100644 index 0000000000..0bb2ff33ed --- /dev/null +++ b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/response/impl/ChangeSetResponseImpl.java @@ -0,0 +1,66 @@ +package org.restlet.ext.odata.batch.response.impl; + +import java.util.ArrayList; +import java.util.List; + +import javax.ws.rs.core.MultivaluedMap; + +import org.restlet.ext.odata.batch.response.BatchResponse; +import org.restlet.ext.odata.batch.response.ChangeSetResponse; +import org.restlet.ext.odata.batch.util.BatchConstants; + +/** + * The Class ChangeSetResponseImpl is implementation class for Changeset + * response.
+ * The changeset response can have a list of multiple responses within,based on + * the requests sent in a chnageset of a batch.
+ * + * copyright 2014 Halliburton
+ * + * @author Amit.Jahagirdar + */ +public class ChangeSetResponseImpl implements ChangeSetResponse { + + /** The responses. */ + List responses = new ArrayList(); + + /** The status. */ + int status = BatchConstants.HTTP_STATUS_OK; + + + /* (non-Javadoc) + * @see org.restlet.ext.odata.batch.response.BatchResponse#getEntity() + */ + @Override + public Object getEntity() { + return responses; + } + + + /* (non-Javadoc) + * @see org.restlet.ext.odata.batch.response.BatchResponse#getStatus() + */ + @Override + public int getStatus() { + return status; + } + + + /* (non-Javadoc) + * @see org.restlet.ext.odata.batch.response.BatchResponse#getHeaders() + */ + @Override + public MultivaluedMap getHeaders() { + return null; + } + + + /* (non-Javadoc) + * @see org.restlet.ext.odata.batch.response.ChangeSetResponse#add(org.restlet.ext.odata.batch.response.BatchResponse) + */ + @Override + public void add(BatchResponse singleResponse) { + responses.add(singleResponse); + } + +} diff --git a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/util/BatchConstants.java b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/util/BatchConstants.java new file mode 100644 index 0000000000..03f1aac662 --- /dev/null +++ b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/util/BatchConstants.java @@ -0,0 +1,38 @@ +package org.restlet.ext.odata.batch.util; + +/** + * Interface to hold constants for Restlet batch implementation. + * + * copyright 2014 Halliburton + * + * @author Amit.Jahagirdar + * + */ +public interface BatchConstants { + + /** The Constant BATCH_ENDPOINT_URI. */ + String BATCH_ENDPOINT_URI = "$batch"; + + String ODATA_VERSION_V3 = "V3"; + + String HTTP_HEADER_CONTENT_TYPE = "Content-Type"; + + String GET_METADATA = "getMetadata"; + + String BATCH_BOUNDARY = "boundary"; + + /** The Constant STATUS. */ + int HTTP_STATUS_OK = 200; + + String CHANGESET_UNDERSCORE = "changeset_"; + + String BATCH_UNDERSCORE = "batch_"; + + String NEW_LINE = System.getProperty("line.separator"); + + String NEW_LINE_BATCH_START = new StringBuilder().append(NEW_LINE).append("--").toString(); + + String NEW_LINE_BATCH_END = new StringBuilder().append("--").append(NEW_LINE).toString(); + + String FORMAT_TYPE_CHARSET_UTF8 = ";charset=utf-8"; +} diff --git a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/util/BodyPart.java b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/util/BodyPart.java new file mode 100644 index 0000000000..f469503457 --- /dev/null +++ b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/util/BodyPart.java @@ -0,0 +1,179 @@ +package org.restlet.ext.odata.batch.util; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; + +import javax.ws.rs.core.MultivaluedMap; + +import org.jvnet.mimepull.MIMEPart; +import org.restlet.data.MediaType; + +/** + * The Class BodyPart is sub part of the Multipart.
+ * Body part represents a single attachment from the Mime message attachments.
+ * + * copyright 2014 Halliburton + * + * @author Amit.Jahagirdar + */ +public class BodyPart implements Closeable { + + /** The mime part. */ + private MIMEPart mimePart; + + /** The media type. */ + private MediaType mediaType; + + /** The entity. */ + private Object entity; + + /** The headers. */ + private MultivaluedMap headers = new HeaderMap(); + + /** The content disposition. */ + private String contentDisposition; + + /** The parent. */ + private Multipart parent; + + /** + * Instantiates a new body part. + * + * @param mimePart + * the mime part + */ + public BodyPart(MIMEPart mimePart) { + this.mimePart = mimePart; + } + + /** + * Instantiates a new body part. + */ + public BodyPart() { + + } + + /** + * Gets the entity. + * + * @return the entity + */ + public Object getEntity() { + return entity; + } + + /** + * Sets the entity. + * + * @param entity + * the new entity + */ + public void setEntity(Object entity) { + this.entity = entity; + } + + /** + * Gets the headers. + * + * @return the headers + */ + public MultivaluedMap getHeaders() { + return headers; + } + + /** + * Sets the headers. + * + * @param headers + * the headers + */ + public void setHeaders(MultivaluedMap headers) { + this.headers = headers; + } + + /** + * Gets the content disposition. + * + * @return the content disposition + */ + public String getContentDisposition() { + return contentDisposition; + } + + /** + * Sets the content disposition. + * + * @param contentDisposition + * the new content disposition + */ + public void setContentDisposition(String contentDisposition) { + this.contentDisposition = contentDisposition; + } + + /** + * Gets the media type. + * + * @return the media type + */ + public MediaType getMediaType() { + return mediaType; + } + + /** + * Sets the media type. + * + * @param mediaType + * the new media type + */ + public void setMediaType(MediaType mediaType) { + this.mediaType = mediaType; + } + + /** + * Sets the parent and add the current bodyPart as child. + * + * @param multipart + * the new parent + */ + public void setParent(Multipart multipart) { + this.parent = multipart; + } + + /** + * Gets the parent. + * + * @return the parent + */ + public Multipart getParent() { + return parent; + } + + /** + * Gets the input stream. + * + * @return the input stream + */ + public InputStream getInputStream() { + return this.mimePart.read(); + } + + /** + * Clean up temporary file(s), if any were utilized. + */ + public void cleanup() { + mimePart.close(); + } + + // Closeable + /** + * Defer to {@link #cleanup}. + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + public void close() throws IOException { + cleanup(); + } + +} diff --git a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/util/HeaderMap.java b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/util/HeaderMap.java new file mode 100644 index 0000000000..23fe8aedb2 --- /dev/null +++ b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/util/HeaderMap.java @@ -0,0 +1,84 @@ +package org.restlet.ext.odata.batch.util; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.ws.rs.core.MultivaluedMap; + +/** + * A {@link MultivaluedMap} with String keys and values backed by a HashMap. + * + * Although keys are stored case-sensitive, all (internal) comparisons are done + * case-insensitive. I.e. {@code get("key")} and {@code get("KEY")} return the + * same values. + */ +public class HeaderMap extends HashMap> implements + MultivaluedMap { + + /** The Constant serialVersionUID. */ + private static final long serialVersionUID = -913599179728662269L; + + @Override + public void putSingle(String key, String value) { + List values = new ArrayList(); + values.add(value); + this.put(key, values); + } + + @Override + public void add(String key, String value) { + List values = this.get(key); + if (values == null) + values = new ArrayList(); + values.add(value); + this.put(key, values); + } + + @Override + public String getFirst(String key) { + List values = this.get(key); + if (values == null || values.size() == 0) + return null; + return values.get(0); + } + + @Override + public boolean containsKey(Object key) { + for (String k : this.keySet()) + if (k.equalsIgnoreCase((String) key)) + return true; + return false; + } + + @Override + public List get(Object key) { + for (String k : this.keySet()) + if (k.equalsIgnoreCase((String) key)) + return super.get(k); + return null; + } + + @Override + public List put(String key, List value) { + List previous = this.remove(key); + super.put(key, value); + return previous; + } + + @Override + public void putAll(Map> map) { + for (Map.Entry> e : map + .entrySet()) + this.put(e.getKey(), e.getValue()); + } + + @Override + public List remove(Object key) { + for (String k : this.keySet()) + if (k.equalsIgnoreCase((String) key)) + return super.remove(k); + return null; + } +} diff --git a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/util/Multipart.java b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/util/Multipart.java new file mode 100644 index 0000000000..08c328ac19 --- /dev/null +++ b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/util/Multipart.java @@ -0,0 +1,72 @@ +package org.restlet.ext.odata.batch.util; + +import java.util.ArrayList; +import java.util.List; + +import org.jvnet.mimepull.MIMEPart; +import org.restlet.data.MediaType; + + +/** + * The Class Multipart is logical representation of the multipart batch request. + * + * + * copyright 2014 Halliburton + * + * @author Amit.Jahagirdar + */ +public class Multipart extends BodyPart { + + /** + * Instantiates a new multipart. + */ + public Multipart() { + super(); + } + + /** + * Instantiates a new multipart. + * + * @param mimePart + * the mime part + */ + public Multipart(MIMEPart mimePart) { + super(mimePart); + } + + /** The body parts. */ + List bodyParts = new ArrayList(); + + /** + * Gets the body parts. + * + * @return the body parts + */ + public List getBodyParts() { + return bodyParts; + } + + /** + * Adds the body parts. + * + * @param bodyPart + * the body part + */ + public void addBodyParts(BodyPart bodyPart) { + // sets the parent multipart on the bodypart and then adds it to the + // list + bodyPart.setParent(this); + this.bodyParts.add(bodyPart); + } + + + @Override + public void setMediaType(MediaType mediaType) { + + if (!mediaType.isCompatible(MediaType.MULTIPART_MIXED)) { + throw new IllegalArgumentException(mediaType.toString()); + } + super.setMediaType(mediaType); + } + +} diff --git a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/util/RestletBatchRequestHelper.java b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/util/RestletBatchRequestHelper.java new file mode 100644 index 0000000000..2201801c7c --- /dev/null +++ b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/batch/util/RestletBatchRequestHelper.java @@ -0,0 +1,385 @@ +package org.restlet.ext.odata.batch.util; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.StringReader; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.ws.rs.core.MultivaluedMap; + +import org.jvnet.mimepull.Header; +import org.jvnet.mimepull.MIMEMessage; +import org.jvnet.mimepull.MIMEPart; +import org.restlet.Request; +import org.restlet.data.MediaType; +import org.restlet.data.Method; +import org.restlet.data.Reference; +import org.restlet.engine.header.HeaderConstants; +import org.restlet.ext.atom.Entry; +import org.restlet.ext.atom.Feed; +import org.restlet.ext.odata.Service; +import org.restlet.ext.odata.batch.request.BatchProperty; +import org.restlet.ext.odata.batch.request.ChangeSetRequest; +import org.restlet.ext.odata.batch.request.ClientBatchRequest; +import org.restlet.ext.odata.batch.request.impl.CreateEntityRequest; +import org.restlet.ext.odata.batch.request.impl.GetEntityRequest; +import org.restlet.ext.odata.batch.response.BatchResponse; +import org.restlet.ext.odata.batch.response.ChangeSetResponse; +import org.restlet.ext.odata.batch.response.impl.BatchResponseImpl; +import org.restlet.ext.odata.batch.response.impl.ChangeSetResponseImpl; +import org.restlet.ext.odata.internal.edm.Metadata; +import org.restlet.ext.odata.xml.AtomFeedHandler; +import org.restlet.representation.Representation; +import org.restlet.representation.StringRepresentation; + +/** + * The Class RestletBatchRequestHelper is helper class.
+ * It has some important functions like parsing changesets responses and + * formatting single requests. Validating the classname from a metadata before + * adding the entity.
+ * + * copyright 2014 Halliburton + * + * @author Amit.Jahagirdar + */ +public class RestletBatchRequestHelper { + + /** + * Format single request. + * + * Creates a String out of the request and the format type provided. + * + * @param req + * the req + * @param formatType + * the format type + * @return the string + */ + public static String formatSingleRequest(Request req, MediaType formatType) { + + StringBuilder sb = new StringBuilder(); + boolean userDefinedContentType = false; + sb.append(HeaderConstants.HEADER_CONTENT_TYPE).append(": "); + + sb.append(MediaType.APPLICATION_ALL).append(BatchConstants.NEW_LINE); + sb.append(HeaderConstants.HEADER_TRANSFER_ENCODING).append(": ") + .append("Binary").append(BatchConstants.NEW_LINE); + sb.append(BatchConstants.NEW_LINE); + + Reference resourceRef = req.getResourceRef(); + String url = resourceRef.getIdentifier(); + + // now, adding this request, 1st URL + sb.append(req.getMethod()).append(" ").append(url) + .append(" HTTP/1.1\r\n"); + + if (!userDefinedContentType + && !(req.getMethod().equals(Method.GET) || req.getMethod() + .equals(Method.DELETE))) { + + sb.append(HeaderConstants.HEADER_CONTENT_TYPE).append(": ") + .append(formatType + BatchConstants.FORMAT_TYPE_CHARSET_UTF8).append(BatchConstants.NEW_LINE); + } + + return sb.toString(); + } + + /** + * Validate and return entity set name. + * + * This method validates if a entityType of the entityClass exists in the metadata for the given service. + * + * If exists then returns the EntitySetName + *
else throws an exception. + * + * + * @param service + * the service + * @param entity + * the entity + * @return the string + * @throws Exception + * the exception + */ + public static String validateAndReturnEntitySetName(Service service, + Class entityClass) throws Exception { + Object object = null; + try { + object = service.getMetadata(); + return ((Metadata) object).getEntityType(entityClass).getName(); + } catch (SecurityException e) { + throw e; + } catch (Exception e) { + throw new Exception("Can't add entity to this entity set " + + entityClass.getName() + + " due to lack of the service's metadata."); + } + } + + /** + * Gets the entity sub path. + * + * @param service + * the service + * @param entity + * the entity + * @return the entity sub path + * @throws Exception + * the exception + */ + public static String getEntitySubPath(Service service, Object entity) + throws Exception { + Object object = null; + try { + object = service.getMetadata(); + return ((Metadata) object).getSubpath(entity).replace("/", ""); + } catch (SecurityException e) { + throw e; + } + } + + /** + * Gets the string representation. + * + * @param service + * the service + * @param entitySetName + * the entity set name + * @param entry + * the entry + * @return the string representation + */ + public static StringRepresentation getStringRepresentation(Service service, + String entitySetName, Entry entry,MediaType type) { + if (entry != null) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + entry.write(baos); + baos.flush(); + } catch (IOException e) { + new RuntimeException("IOException during creating a string representation"+ e); + } + + StringRepresentation r = new StringRepresentation(baos.toString(),type); + return r; + } + return null; + } + + /** + * Parses the single operation response. + * + * This method parses the response for a single request within the changeset or a singel GET request. + *
Then creates a batch response and populates the entity by parsing the response output. + * + * @param topVersion + * the top version + * @param content + * the content + * @param so + * the so + * @param formatType + * the format type + * @param service + * the service + * @return the batch response + */ + public static BatchResponse parseSingleOperationResponse(String topVersion, + String content, ClientBatchRequest so, MediaType formatType, + Service service) { + // first create a buffered reader + BufferedReader reader = new BufferedReader(new StringReader(content)); + try { + // 1st line should be status line line HTTP/1.1 200 OK + String line = reader.readLine(); + String[] statusLine = line.split("\\s"); + int status = Integer.parseInt(statusLine[1]); + + boolean isHeader = true; + Map headers = new HashMap(); + MultivaluedMap inboundHeaders = new HeaderMap(); + StringBuilder sb = new StringBuilder(); + while ((line = reader.readLine()) != null) { + // \n\n indicates the end of header for the response + if (line.isEmpty()) { + isHeader = false; + continue; + } + if (isHeader) { + int idx = line.indexOf(":"); + String key = line.substring(0, idx).toUpperCase().trim(); + String value = line.substring(idx + 1).trim(); + headers.put(key, value); + inboundHeaders.add(key, value); + } else { + sb.append(line); + } + } + + Object result = null; + if (inboundHeaders.containsKey(BatchConstants.HTTP_HEADER_CONTENT_TYPE)) { + if (so instanceof CreateEntityRequest + || so instanceof GetEntityRequest) { + BatchProperty bp = (BatchProperty) so; + AtomFeedHandler aFHandler = new AtomFeedHandler( + bp.getEntitySetName(), bp.getEntityType(), + bp.getEntityClass(), + (Metadata) service.getMetadata()); + result = RestletBatchRequestHelper.getEntity( + new StringRepresentation(sb.toString()), aFHandler); + } + } + BatchResponseImpl batchResponse = new BatchResponseImpl(status, + inboundHeaders, result); + return batchResponse; + } catch (IOException e) { + throw new RuntimeException( + "IOException in ParseSingleOperationResponse", e); + } + + } + + /** + * Parses the change set response. + *
This method parses the response and from within parses each request within the changeset + *
and creates a complete changeset response out of it. + * + * @param oDataVersion + * the o data version + * @param contentList + * the content list + * @param csr + * the csr + * @param formatType + * the format type + * @param service + * the service + * @return the batch response + */ + public static BatchResponse parseChangeSetResponse(String oDataVersion, + List contentList, ChangeSetRequest csr, + MediaType formatType, Service service) { + // the change set will return another list of the result + ChangeSetResponse changeSetResponse = new ChangeSetResponseImpl(); + int j = 0; + for (String content : contentList) { + ClientBatchRequest so = csr.getReqs().get(j); + BatchResponse response = RestletBatchRequestHelper + .parseSingleOperationResponse(oDataVersion, content, so, + formatType, service); + changeSetResponse.add(response); + j++; + } + return changeSetResponse; + } + + /** + * Gets the entity by parsing the representation using the feedhandler sent as a parameter. + * + * @param + * the generic type + * @param rep + * the rep + * @param feedHandler + * the feed handler + * @return the entity + * @throws IOException + * Signals that an I/O exception has occurred. + */ + public static T getEntity(Representation rep, + AtomFeedHandler feedHandler) throws IOException { + Feed feed = new Feed(); + feedHandler.setFeed(feed); + feedHandler.parse(rep.getReader()); + return feedHandler.getEntities().get(0); + } + + + /** + * Gets the string from input stream. + * + * @param is + * the is + * @return the string from input stream + */ + public static String getStringFromInputStream(InputStream is) { + BufferedReader br = null; + StringBuilder sb = new StringBuilder(); + String line; + try { + br = new BufferedReader(new InputStreamReader(is)); + while ((line = br.readLine()) != null) { + sb.append(line).append(BatchConstants.NEW_LINE); + } + } catch (IOException e) { + throw new RuntimeException("IOException occured while resding stream"+ e); + } finally { + if (br != null) { + try { + // br.close(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + return sb.toString(); + } + + /** + * Creates the multipart. + * + * A Multipart is a logical representation of a batch request or Chnagset. + *
It is a set of multiple http requests/response. + * + * @param is + * the is + * @param mediaType + * the media type + * @return the multipart + * @throws IOException + * Signals that an I/O exception has occurred. + */ + public static Multipart createMultipart(InputStream is, MediaType mediaType) + throws IOException { + // create a multipart + Multipart multipart = new Multipart(); + // set its mediatype + multipart.setMediaType(mediaType); + + MIMEMessage mimeMessage = new MIMEMessage(is, mediaType.getParameters() + .getFirstValue(BatchConstants.BATCH_BOUNDARY)); + List attachments = mimeMessage.getAttachments(); + for (MIMEPart mimePart : attachments) { + BodyPart bodyPart = new BodyPart(mimePart); + // copy headers into bodyparts + copyHeaders(bodyPart, mimePart); + bodyPart.setMediaType(new MediaType(bodyPart.getHeaders().getFirst( + BatchConstants.HTTP_HEADER_CONTENT_TYPE))); + multipart.addBodyParts(bodyPart); + + } + return multipart; + } + + /** + * Copies header information from mimePart to body part. + * + * @param bodyPart + * the body part + * @param mimePart + * the mime part + */ + public static void copyHeaders(BodyPart bodyPart, MIMEPart mimePart) { + MultivaluedMap bpHeaders = bodyPart.getHeaders(); + List mHeaders = mimePart.getAllHeaders(); + for (Header header : mHeaders) { + bpHeaders.add(header.getName(), header.getValue()); + } + } + +} diff --git a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/AtomContentFunctionHandler.java b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/AtomContentFunctionHandler.java new file mode 100644 index 0000000000..fe5a5b79f9 --- /dev/null +++ b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/AtomContentFunctionHandler.java @@ -0,0 +1,74 @@ +package org.restlet.ext.odata.internal; + +import java.io.IOException; +import java.util.List; + +import javax.xml.namespace.QName; +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.events.StartElement; +import javax.xml.stream.events.XMLEvent; + +import org.restlet.Context; +import org.restlet.ext.odata.internal.edm.TypeUtils; +import org.restlet.ext.xml.format.XmlFormatParser; +import org.restlet.representation.Representation; + +/** + * This class is added for parsing the representation result provided by RESLET as a response in the + * respective object/ return type for the functions/actions. + * + * @author Akshay + */ +public class AtomContentFunctionHandler extends XmlFormatParser implements FunctionContentHandler { + + /* (non-Javadoc) + * @see org.restlet.ext.odata.internal.FunctionContentHandler#parseResult(java.lang.Class, org.restlet.representation.Representation, + * java.lang.String, java.util.List) + */ + @SuppressWarnings({ "unused", "unchecked", "rawtypes" }) + public Object parseResult(Class classType, + Representation representation, String functionName, List entity) { + try { + XMLInputFactory factory = XMLInputFactory.newInstance(); + XMLEventReader reader = factory.createXMLEventReader(representation.getReader()); + + while (reader.hasNext()) { + XMLEvent event = reader.nextEvent(); + StartElement startElement = null; + + if (event.isEndElement() + && startElement != null + && event.asEndElement().getName() + .equals(startElement.getName())) { + break; + } + + if (isStartElement(event, new QName(XmlFormatParser.NS_DATASERVICES, functionName))) { + startElement = event.asStartElement(); + } + + if (event.isStartElement() + && event.asStartElement().getName().getNamespaceURI() + .equals(NS_DATASERVICES) + && event.asStartElement().getName() + .equals(DATASERVICES_ELEMENT)) { + if (entity instanceof List) { + Object value = TypeUtils.convert(classType, + reader.getElementText()); + ((List) entity).add(value); + } + } + } + } catch (XMLStreamException e) { + Context.getCurrentLogger().warning( + "Cannot parse the xml due to Stream Exception: " + e.getMessage()); + } catch (IOException e) { + Context.getCurrentLogger().warning( + "Cannot parse the xml due to IO Exception: " + e.getMessage()); + } + return entity; + } + +} \ No newline at end of file diff --git a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/FunctionContentHandler.java b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/FunctionContentHandler.java new file mode 100644 index 0000000000..4621eef07b --- /dev/null +++ b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/FunctionContentHandler.java @@ -0,0 +1,26 @@ +package org.restlet.ext.odata.internal; +import java.util.List; + +import org.restlet.representation.Representation; + + +/** + * The interface for parsing the representation result provided by RESLET as a response in the + * respective object/ return type for the functions/actions. + * + * @author Shantanu + * + */ +public interface FunctionContentHandler { + + /** + * Parses the result. + * + * @param classType - Class + * @param representation - Representation + * @param functionName - Name of the function + * @param returnType - The return type + * @return expected result object for a function or action + */ + Object parseResult(Class classType, Representation representation, String functionName, List returnType); +} diff --git a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/JsonContentFunctionHandler.java b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/JsonContentFunctionHandler.java new file mode 100644 index 0000000000..84def9163a --- /dev/null +++ b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/JsonContentFunctionHandler.java @@ -0,0 +1,95 @@ +package org.restlet.ext.odata.internal; + +import java.io.IOException; +import java.util.List; + +import javax.xml.namespace.QName; +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.events.XMLEvent; + +import org.restlet.Context; +import org.restlet.ext.odata.xml.AtomFeedHandler; +import org.restlet.ext.xml.format.XmlFormatParser; +import org.restlet.representation.Representation; + +import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; + + +/** + * This class is added for parsing the representation result provided by RESLET as a response in the + * expected object/return type for the functions/actions. + * + * @author Shantanu + */ +public class JsonContentFunctionHandler extends XmlFormatParser implements FunctionContentHandler { + + + /* (non-Javadoc) + * @see org.restlet.ext.odata.internal.FunctionContentHandler#parseResult(java.lang.Class, org.restlet.representation.Representation, + * java.lang.String, java.util.List) + */ + public Object parseResult(Class c, Representation representation, String functionName,List entity) { + try { + String jsonString = null; + XMLInputFactory factory = XMLInputFactory.newInstance(); + XMLEventReader eventReader; + eventReader = factory.createXMLEventReader(representation.getReader()); + + while (eventReader.hasNext()) { + XMLEvent event; + event = eventReader.nextEvent(); + if(isStartElement(event, new QName(XmlFormatParser.NS_DATASERVICES, functionName))){ + jsonString = AtomFeedHandler.innerText(eventReader, event.asStartElement()); + } + } + + if (jsonString != null && !jsonString.equals("")) { + StringBuilder jsonStrBuilder = new StringBuilder(); + jsonStrBuilder.append("{"); + int beginIndex = jsonString.indexOf("{"); + int endIndex = jsonString.indexOf("}"); + String jsonStringWithoutBraces = jsonString.substring(beginIndex + 1, endIndex); + + if (jsonStringWithoutBraces != null) { + String[] propertiesSplit = jsonStringWithoutBraces.trim().split(","); + + for (int i = 0; i < propertiesSplit.length; i++) { + String value = propertiesSplit[i]; + + if (value.contains(":")) { + String[] split = value.trim().split(":"); + String valueToNormalise = split[0].trim(); + String normalisedValue = valueToNormalise.substring(0, 1) + + valueToNormalise.substring(1, 2).toLowerCase() + + valueToNormalise.substring(2); + jsonStrBuilder.append(normalisedValue); + jsonStrBuilder.append(":"); + jsonStrBuilder.append(split[1]); + } + + if (!(i == (propertiesSplit.length - 1))) { + jsonStrBuilder.append(","); + } + } + } + + jsonStrBuilder.append("}"); + return new Gson().fromJson(jsonStrBuilder.toString(), c); + } + } catch (XMLStreamException e) { + Context.getCurrentLogger().warning( + "Cannot parse the xml due to Stream Exception: " + e.getMessage()); + } catch (IOException e) { + Context.getCurrentLogger().warning( + "Cannot parse the xml due to IO Exception: " + e.getMessage()); + } catch (JsonSyntaxException e) { + Context.getCurrentLogger().warning( + "Cannot parse the xml due to Json Syntax Exception: " + e.getMessage()); + } + return null; + } + +} diff --git a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/edm/EntityType.java b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/edm/EntityType.java index 073803f4a2..82c5029c0f 100644 --- a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/edm/EntityType.java +++ b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/edm/EntityType.java @@ -129,6 +129,7 @@ public Set getImportedJavaClasses() { for (NavigationProperty property : getAssociations()) { if (property.getToRole().isToMany()) { result.add("java.util.List"); + result.add("java.util.ArrayList"); break; } } diff --git a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/edm/FunctionImport.java b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/edm/FunctionImport.java index b5a036fcfd..a9c5250bca 100644 --- a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/edm/FunctionImport.java +++ b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/edm/FunctionImport.java @@ -68,6 +68,32 @@ public class FunctionImport extends NamedObject { /** The return type of this function. */ private String returnType; + + /** The java return type of this function. */ + private String javaReturnType; + + /** The is complex. */ + private boolean complex; + + /** The is collection. */ + private boolean collection; + + /** The is simple. */ + private boolean simple; + + /** + * @return the javaReturnType + */ + public String getJavaReturnType() { + return javaReturnType; + } + + /** + * @param javaReturnType the javaReturnType to set + */ + public void setJavaReturnType(String javaReturnType) { + this.javaReturnType = javaReturnType; + } /** * Constructor. @@ -298,4 +324,47 @@ public void setReturnType(String returnType) { this.returnType = returnType; } + /** + * @return the isComplex + */ + public boolean isComplex() { + return complex; + } + + /** + * @param isComplex the isComplex to set + */ + public void setComplex(boolean isComplex) { + this.complex = isComplex; + } + + /** + * @return the isCollection + */ + public boolean isCollection() { + return collection; + } + + /** + * @param isCollection the isCollection to set + */ + public void setCollection(boolean isCollection) { + this.collection = isCollection; + } + + /** + * @return the isSimple + */ + public boolean isSimple() { + return simple; + } + + /** + * @param isSimple the isSimple to set + */ + public void setSimple(boolean isSimple) { + this.simple = isSimple; + } + + } diff --git a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/edm/Metadata.java b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/edm/Metadata.java index a3c5ea47a2..7ffa69a339 100644 --- a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/edm/Metadata.java +++ b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/edm/Metadata.java @@ -335,6 +335,14 @@ public Property getProperty(Object entity, String propertyName) { break; } } + for(ComplexProperty property : et.getComplexProperties()){ + if (property.getName().equals(propertyName) + || property.getNormalizedName() + .equals(propertyName)) { + result = property; + break; + } + } } else { ComplexType ct = getComplexType(entity.getClass()); if (ct != null) { @@ -346,6 +354,14 @@ public Property getProperty(Object entity, String propertyName) { break; } } + for(ComplexProperty property : ct.getComplexProperties()){ + if (property.getName().equals(propertyName) + || property.getNormalizedName() + .equals(propertyName)) { + result = property; + break; + } + } } } diff --git a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/edm/MetadataReader.java b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/edm/MetadataReader.java index 750534e11e..183a3b6ade 100644 --- a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/edm/MetadataReader.java +++ b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/edm/MetadataReader.java @@ -41,6 +41,8 @@ import org.restlet.data.MediaType; import org.restlet.data.Method; import org.restlet.ext.odata.Service; +import org.restlet.ext.odata.internal.reflect.ReflectUtils; +import org.restlet.ext.xml.format.XmlFormatParser; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; @@ -556,20 +558,30 @@ public void startElement(String uri, String localName, String name, if (type.toLowerCase().startsWith("edm.")) { property = new Property(attrs.getValue("Name")); property.setType(new Type(attrs.getValue("Type"))); + property.setDefaultValue(attrs.getValue("Default")); + } else if (type.toLowerCase().startsWith("collection")) { + ComplexProperty p = new ComplexProperty(attrs.getValue("Name")); + String edmType = TypeUtils.getClassType(type); + p.setComplexType(new ComplexType("List<"+edmType+">")); + property = p; + property.setDefaultValue("new ArrayList<"+edmType+">()"); } else { ComplexProperty p = new ComplexProperty(attrs.getValue("Name")); p.setComplexType(new ComplexType(attrs.getValue("Type"))); property = p; + property.setDefaultValue(attrs.getValue("Default")); } - - property.setDefaultValue(attrs.getValue("Default")); // If no value is specified, the nullable facet defaults to true. // cf http://www.odata.org/documentation/odata-v3-documentation/common-schema-definition-language-csdl/#531_The_edmNullable_Attribute String nullable = attrs.getValue("Nullable"); if (nullable == null) { property.setNullable(true); } else { - property.setNullable(Boolean.parseBoolean(nullable)); + boolean isNullable = Boolean.parseBoolean(nullable); + property.setNullable(isNullable); + if(!isNullable){ + property.getAnnotations().add("NotNull"); + } } // ConcurrencyMode if ("fixed".equalsIgnoreCase(attrs.getValue("ConcurrencyMode"))) { @@ -585,8 +597,32 @@ public void startElement(String uri, String localName, String name, if (str != null) { property.setMediaType(MediaType.valueOf(str)); } - - if (getState() == State.ENTITY_TYPE) { + + String systemGenerated = attrs.getValue( + XmlFormatParser.NS_CUSTOM_EDMANNOTATION, "IsSystemGenerated"); + if (systemGenerated != null) { + boolean isSystemGenerated = Boolean.parseBoolean(systemGenerated); + if(isSystemGenerated){ + property.getAnnotations().add("SystemGenerated"); + } + } + + String propName = attrs.getValue("Name"); + //add annotation to the property if it property name is same as java reserved key word. + if(propName!=null&&ReflectUtils.isReservedWord(propName.toLowerCase())){ + property.getAnnotations().add("JavaReservedKeyWord"); + } + + if(currentEntityType!=null){ + List keys = currentEntityType.getKeys(); + for(Property prop : keys){ + if(prop.getName().equalsIgnoreCase(attrs.getValue("Name"))){ + property.getAnnotations().add("PrimaryKey"); + } + } + } + + if (getState() == State.ENTITY_TYPE) { pushState(State.ENTITY_TYPE_PROPERTY); if (property instanceof ComplexProperty) { this.currentEntityType.getComplexProperties().add( @@ -662,11 +698,45 @@ public void startElement(String uri, String localName, String name, currentFunctionImport.setMethodAccess(attrs .getValue("MethodAccess")); currentFunctionImport.setMetadata(currentMetadata); - - String str = attrs.getValue( + String elementType = attrs.getValue(XmlFormatParser.NS_CUSTOM_EDMANNOTATION, "elementType"); + if (elementType != null) { + String[] split = elementType.split("\\."); + String className = ReflectUtils.normalize(split[1]); + className = className.substring(0, 1).toUpperCase() + className.substring(1); + currentFunctionImport.setJavaReturnType(className); + currentFunctionImport.setComplex(true); + } else if (attrs.getValue("ReturnType") != null) { + if (attrs.getValue("ReturnType").startsWith("Collection")) { + String type = TypeUtils.getClassType(attrs.getValue("ReturnType")); + currentFunctionImport.setJavaReturnType("List<"+type+">"); + currentFunctionImport.setReturnType(type); + currentFunctionImport.setCollection(true); + }else { + currentFunctionImport.setSimple(true); + currentFunctionImport.setJavaReturnType(TypeUtils + .toJavaTypeName(attrs.getValue("ReturnType"))); + } + }else { + currentFunctionImport.setComplex(true); + currentFunctionImport.setJavaReturnType("void"); + } + + + String httpMethod = attrs.getValue( Service.WCF_DATASERVICES_METADATA_NAMESPACE, "HttpMethod"); - if (str != null) { - currentFunctionImport.setMethod(Method.valueOf(str)); + if (httpMethod != null) { + currentFunctionImport.setMethod(Method.valueOf(httpMethod)); + } + else{ + //Default to POST if isSideEffecting="true" and no Http method provided + Boolean isSideEffecting = Boolean.parseBoolean(attrs.getValue("IsSideEffecting")); + if(isSideEffecting){ + currentFunctionImport.setMethod(Method.valueOf("POST")); + } + else{ + //Default to GET is isSideEffecting="false" and no Http method provided + currentFunctionImport.setMethod(Method.valueOf("GET")); + } } if (State.ENTITY_CONTAINER == getState()) { @@ -679,6 +749,12 @@ public void startElement(String uri, String localName, String name, if (State.FUNCTION_IMPORT == getState()) { Parameter parameter = new Parameter(attrs.getValue("Name")); parameter.setType(attrs.getValue("Type")); + if(attrs.getValue("Type").startsWith("Collection")){ + String edmType = TypeUtils.getClassType(attrs.getValue("Type")); + parameter.setJavaType("List<"+edmType+">"); + }else{ + parameter.setJavaType(TypeUtils.toJavaTypeName(attrs.getValue("Type"))); + } parameter.setMode(attrs.getValue("Mode")); String str = attrs.getValue("MaxLength"); if (str != null) { diff --git a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/edm/NamedObject.java b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/edm/NamedObject.java index e1d837745b..f57a47dcdf 100644 --- a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/edm/NamedObject.java +++ b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/edm/NamedObject.java @@ -58,7 +58,12 @@ public class NamedObject { public NamedObject(String name) { super(); this.name = name; - this.normalizedName = ReflectUtils.normalize(name); + String normalizedName = ReflectUtils.normalize(name); + if (ReflectUtils.isReservedWord(normalizedName)) { + normalizedName = "_" + normalizedName; + } + this.normalizedName = normalizedName; + } /** diff --git a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/edm/ODataType.java b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/edm/ODataType.java index 24e6ee3ad2..ad52a74065 100644 --- a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/edm/ODataType.java +++ b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/edm/ODataType.java @@ -145,11 +145,20 @@ public Set getImportedJavaClasses() { } for (ComplexProperty property : getComplexProperties()) { - if (property.getComplexType() != null) { - if (!property.getComplexType().getSchema().equals(getSchema())) { - result.add(property.getComplexType().getFullClassName()); - } - } + if (property.getComplexType() != null) { + if (property.getComplexType().getSchema() != null) { + if (!property.getComplexType().getSchema() + .equals(getSchema())) { + result.add(property.getComplexType().getFullClassName()); + } + } else { + String propertyType = property.getComplexType().getName(); + if (propertyType.toLowerCase().startsWith("list")) { + result.add("java.util.List"); + result.add("java.util.ArrayList"); + } + } + } } return result; diff --git a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/edm/Parameter.java b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/edm/Parameter.java index dd0ae4e4a1..dbcb077c01 100644 --- a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/edm/Parameter.java +++ b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/edm/Parameter.java @@ -57,6 +57,9 @@ public class Parameter extends NamedObject { /** The type of the parameter. */ private String type; + + /** The type of the parameter. */ + private String javaType; /** * Constructor. @@ -163,4 +166,18 @@ public void setType(String type) { this.type = type; } + /** + * @return the javaType + */ + public String getJavaType() { + return javaType; + } + + /** + * @param javaType the javaType to set + */ + public void setJavaType(String javaType) { + this.javaType = javaType; + } + } diff --git a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/edm/Property.java b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/edm/Property.java index de65a82f11..085a0d2a16 100644 --- a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/edm/Property.java +++ b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/edm/Property.java @@ -33,6 +33,9 @@ package org.restlet.ext.odata.internal.edm; +import java.util.ArrayList; +import java.util.List; + import org.restlet.data.MediaType; import org.restlet.ext.odata.internal.reflect.ReflectUtils; @@ -65,6 +68,8 @@ public class Property extends NamedObject { /** The type of the property. */ private Type type; + + private List annotations = new ArrayList(); /** * Constructor. @@ -231,4 +236,17 @@ public void setType(Type type) { this.type = type; } + /** + * @return the annotations + */ + public List getAnnotations() { + return annotations; + } + + /** + * @param annotations the annotations to set + */ + public void setAnnotations(List annotations) { + this.annotations = annotations; + } } diff --git a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/edm/Type.java b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/edm/Type.java index 630988094e..2da1772c83 100644 --- a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/edm/Type.java +++ b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/edm/Type.java @@ -76,6 +76,8 @@ public Set getImportedJavaClasses() { result.add(getJavaClass().getName()); } else if (getName().endsWith("DateTimeOffset")) { result.add(getJavaClass().getName()); + } else if (getName().endsWith("Stream")) { + result.add(getJavaClass().getName()); } return result; diff --git a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/edm/TypeUtils.java b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/edm/TypeUtils.java index 88c07c2d58..f54a5980d1 100644 --- a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/edm/TypeUtils.java +++ b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/edm/TypeUtils.java @@ -33,6 +33,9 @@ package org.restlet.ext.odata.internal.edm; +import java.beans.PropertyEditor; +import java.beans.PropertyEditorManager; +import java.math.BigDecimal; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.text.NumberFormat; @@ -45,6 +48,7 @@ import org.restlet.engine.util.Base64; import org.restlet.engine.util.DateUtils; import org.restlet.ext.odata.internal.reflect.ReflectUtils; +import org.restlet.ext.odata.streaming.StreamReference; /** * Handle type operations. @@ -102,11 +106,11 @@ public static Object fromEdm(String value, String adoNetType) { } else if (adoNetType.endsWith("Time")) { result = timeFormat.parseObject(value); } else if (adoNetType.endsWith("Decimal")) { - result = decimalFormat.parseObject(value); + result = BigDecimal.valueOf((Long) decimalFormat.parseObject(value)); } else if (adoNetType.endsWith("Single")) { - result = singleFormat.parseObject(value); + result = Float.valueOf(singleFormat.parseObject(value).toString()); } else if (adoNetType.endsWith("Double")) { - result = doubleFormat.parseObject(value); + result = Double.valueOf(doubleFormat.parseObject(value).toString()); } else if (adoNetType.endsWith("Guid")) { result = value; } else if (adoNetType.endsWith("Int16")) { @@ -206,6 +210,9 @@ public static String getLiteralForm(String value, String adoNetType) { result = "guid'" + value + "'"; } else if (adoNetType.endsWith("String")) { result = "'" + value + "'"; + }else if (adoNetType.endsWith("Double") || adoNetType.endsWith("Float") + || adoNetType.endsWith("Integer") || adoNetType.endsWith("Long")) { + result = value ; } } catch (Exception e) { Context.getCurrentLogger().warning( @@ -288,6 +295,8 @@ public static String toEdm(Object value, Type type) { } else if (adoNetType.endsWith("Decimal")) { if ((Double.class).isAssignableFrom(value.getClass())) { result = toEdmDecimal((Double) value); + }else if ((BigDecimal.class).isAssignableFrom(value.getClass())) { + result = toEdmDecimal((BigDecimal) value); } } else if (adoNetType.endsWith("Single")) { if ((Float.class).isAssignableFrom(value.getClass())) { @@ -386,6 +395,18 @@ public static String toEdmDateTime(Date value) { public static String toEdmDecimal(double value) { return decimalFormat.format(value); } + + /** + * Convert the given value to the String representation of a EDM Decimal + * value. + * + * @param value + * The value to convert. + * @return The value converted as String object. + */ + public static String toEdmDecimal(BigDecimal value) { + return decimalFormat.format(value); + } /** * Convert the given value to the String representation of a EDM Double @@ -475,6 +496,8 @@ public static String toEdmKey(Object value, Type type) { } else if (adoNetType.endsWith("Decimal")) { if ((Double.class).isAssignableFrom(value.getClass())) { result = toEdmDecimal((Double) value); + } else if ((BigDecimal.class).isAssignableFrom(value.getClass())) { + result = toEdmDecimal((BigDecimal) value); } } else if (adoNetType.endsWith("Single")) { if ((Float.class).isAssignableFrom(value.getClass())) { @@ -570,9 +593,9 @@ public static Class toJavaClass(String edmTypeName) { } else if (edmTypeName.endsWith("Time")) { result = Long.class; } else if (edmTypeName.endsWith("Decimal")) { - result = Double.class; + result = BigDecimal.class; } else if (edmTypeName.endsWith("Single")) { - result = Double.class; + result = Float.class; } else if (edmTypeName.endsWith("Double")) { result = Double.class; } else if (edmTypeName.endsWith("Guid")) { @@ -587,6 +610,40 @@ public static Class toJavaClass(String edmTypeName) { result = Byte.class; } else if (edmTypeName.endsWith("String")) { result = String.class; + } else if (edmTypeName.startsWith("List")) { + result = List.class; + } else if (edmTypeName.endsWith("Stream")) { + result = StreamReference.class; + } + + return result; + } + + public static String toEdmType(String edmTypeName) { + String result = "Object"; + edmTypeName = edmTypeName.toLowerCase(); + if (edmTypeName.endsWith("byte")) { + result = "Edm.Byte"; + } else if (edmTypeName.endsWith("boolean")) { + result = "Edm.Boolean"; + } else if (edmTypeName.endsWith("date")) { + result = "Edm.DateTime"; + } else if (edmTypeName.endsWith("bigdecimal")) { + result = "Edm.Decimal"; + } else if (edmTypeName.endsWith("float")) { + result = "Edm.Single"; + } else if (edmTypeName.endsWith("double")) { + result = "Edm.Double"; + } else if (edmTypeName.endsWith("short")) { + result = "Edm.Int16"; + } else if (edmTypeName.endsWith("integer")) { + result = "Edm.Int32"; + } else if (edmTypeName.endsWith("long")) { + result = "Edm.Int64"; + } else if (edmTypeName.endsWith("string")) { + result = "Edm.String"; + } else if (edmTypeName.endsWith("streamreference")) { + result = "Edm.Stream"; } return result; @@ -604,7 +661,7 @@ public static String toJavaTypeName(String edmTypeName) { if (edmTypeName.endsWith("Binary")) { result = "byte[]"; } else if (edmTypeName.endsWith("Boolean")) { - result = "boolean"; + result = "Boolean"; } else if (edmTypeName.endsWith("DateTime")) { result = "Date"; } else if (edmTypeName.endsWith("DateTimeOffset")) { @@ -612,26 +669,86 @@ public static String toJavaTypeName(String edmTypeName) { } else if (edmTypeName.endsWith("Time")) { result = "long"; } else if (edmTypeName.endsWith("Decimal")) { - result = "double"; + result = "java.math.BigDecimal"; } else if (edmTypeName.endsWith("Single")) { - result = "double"; + result = "Float"; } else if (edmTypeName.endsWith("Double")) { - result = "double"; + result = "Double"; } else if (edmTypeName.endsWith("Guid")) { result = "String"; } else if (edmTypeName.endsWith("Int16")) { - result = "short"; + result = "Short"; } else if (edmTypeName.endsWith("Int32")) { - result = "int"; + result = "Integer"; } else if (edmTypeName.endsWith("Int64")) { - result = "long"; + result = "Long"; } else if (edmTypeName.endsWith("Byte")) { - result = "byte"; + result = "Byte"; } else if (edmTypeName.endsWith("String")) { result = "String"; + } else if (edmTypeName.endsWith("Stream")) { + result = "StreamReference"; } return result; } + + /** + * Gets the class type. + * + * @param Edm Type. For e.g. Edm.Double + * @return the respective java class. For e.g. java.lang.Double in case of Edm.Double + */ + public static String getClassType(String type) { + try { + String edmType = TypeUtils.getCollectionType(type); + if (edmType == null){ + return null; + } + if (edmType.toLowerCase().startsWith("edm.")) { + Class javaClass = TypeUtils.toJavaClass(edmType); + return javaClass.getName(); + } else { + String[] split = edmType.split("\\."); + StringBuilder sb = new StringBuilder(); + for (int i = 1; i < split.length; i++) { + sb.append(split[i]); + } + return sb.toString(); + } + } catch (Exception e) { + return null; + } + } + + /** + * Gets the collection type. + * + * @param String as per metadata. For e.g. Collection(Edm.Double) + * @return the respective edm type. For e.g. Edm.Double in case of Collection(Edm.Double) + */ + public static String getCollectionType(String type){ + if(type != null && type.length()>11){ + return type.substring(11, type.length() - 1); + } + return null; + } + + /** + * Convert the String value to primitive class type. + * + * @param targetType the target type + * @param text the text + * @return the object + */ + public static Object convert(Class targetType, String text) { + try { + PropertyEditor editor = PropertyEditorManager.findEditor(targetType); + editor.setAsText(text); + return editor.getValue(); + } catch (Exception e) { + return text; + } + } } diff --git a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/reflect/ReflectUtils.java b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/reflect/ReflectUtils.java index 2eeb5376b6..15671fb816 100644 --- a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/reflect/ReflectUtils.java +++ b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/reflect/ReflectUtils.java @@ -253,6 +253,7 @@ public static void invokeSetter(Object entity, String propertyName, if ((method.getParameterTypes() != null) && (method.getParameterTypes().length == 1)) { setter = method; + break; } } } @@ -368,6 +369,9 @@ public static boolean isReservedWord(String name) { */ public static String normalize(String name) { String result = null; + if(name != null && name.toLowerCase().startsWith("list")){ + return name; + } if (name != null) { // Build the normalized name according to the java naming rules StringBuilder b = new StringBuilder(); @@ -524,4 +528,32 @@ public static void setProperty(Object entity, String propertyName, invokeSetter(entity, propertyName, propertyValue, null); } + + /** + * Gets the property object. + * Creates the instance of property if not already created. + * + * @param the generic type + * @param entity the entity + * @param propertyName the property name + * @return the property object + */ + public static Object getPropertyObject(T entity, String propertyName) { + Object o = null; + try { + o = ReflectUtils.invokeGetter(entity, propertyName); + Field[] fields = entity.getClass().getDeclaredFields(); + if (o == null) { + for (Field field : fields) { + if (field.getName().equalsIgnoreCase(propertyName)) { + o = field.getType().newInstance(); + break; + } + } + } + } catch (Exception e) { + logger.warning("Can't get the property: " + e.getMessage()); + } + return o; + } } diff --git a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/templates/complexType.ftl b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/templates/complexType.ftl index 02a4fb9792..fdabb14fd1 100755 --- a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/templates/complexType.ftl +++ b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/templates/complexType.ftl @@ -64,9 +64,9 @@ public <#if type.abstractType>abstract class ${className} { <#list type.complexProperties?sort_by("name") as property> <#if property.complexType??> - private ${property.complexType.className} ${property.propertyName}; + private ${property.complexType.className} ${property.propertyName}<#if property.defaultValue??> = ${property.defaultValue}; <#else> - // private [error: no defined type] ${property.propertyName}; + // private [error: no defined type] ${property.propertyName}<#if property.defaultValue??> = ${property.defaultValue}; diff --git a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/templates/entityType.ftl b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/templates/entityType.ftl index 853e3e152c..889f347b22 100755 --- a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/templates/entityType.ftl +++ b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/templates/entityType.ftl @@ -40,10 +40,14 @@ import ${clazz}; <#list type.importedTypes?sort as t> -import ${t.fullClassName}; +import ${packageName}.${t.className}; +import org.restlet.ext.odata.validation.annotation.NotNull; +import org.restlet.ext.odata.validation.annotation.PrimaryKey; +import org.restlet.ext.odata.validation.annotation.SystemGenerated; +import org.restlet.ext.odata.validation.annotation.JavaReservedKeyWord; <#compress> /** @@ -57,6 +61,9 @@ import ${t.fullClassName}; public <#if type.abstractType>abstract class ${className} { <#list type.properties?sort_by("name") as property> + <#list property.annotations as annotation> + @${annotation} + <#if property.type??> private ${property.type.className} ${property.propertyName}<#if property.defaultValue??> = property.defaultValue; <#else> @@ -65,13 +72,13 @@ public <#if type.abstractType>abstract class ${className} { <#list type.complexProperties?sort_by("name") as property> <#if property.complexType??> - private ${property.complexType.className} ${property.propertyName}; + private ${property.complexType.className} ${property.propertyName}<#if property.defaultValue??> = ${property.defaultValue};; <#else> - // private [error: no defined type] ${property.propertyName}; + // private [error: no defined type] ${property.propertyName}<#if property.defaultValue??> = ${property.defaultValue};; <#list type.associations?sort_by("name") as association> - private <#if association.toRole.toMany>List<${association.toRole.type.className}><#else>${association.toRole.type.className} ${association.normalizedName}; + private <#if association.toRole.toMany> List<${association.toRole.type.className}> <#else> ${association.toRole.type.className} ${association.normalizedName} <#if association.toRole.toMany> = new ArrayList<${association.toRole.type.className}>(); <#if type.blob> /** The reference of the underlying blob representation. */ diff --git a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/templates/service.ftl b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/templates/service.ftl index c9958fb271..373be94a2c 100755 --- a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/templates/service.ftl +++ b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/internal/templates/service.ftl @@ -30,9 +30,11 @@ * * Restlet is a registered trademark of Restlet S.A.S. */ +package ${packageName}; +import java.util.ArrayList; import java.util.List; - +import java.util.Collection; import org.restlet.data.MediaType; import org.restlet.data.Preference; import org.restlet.data.Reference; @@ -40,9 +42,25 @@ import org.restlet.ext.odata.Query; import org.restlet.representation.Representation; import org.restlet.resource.ClientResource; import org.restlet.resource.ResourceException; +import org.restlet.data.ChallengeScheme; +import org.restlet.data.ChallengeResponse; +import org.restlet.data.Parameter; +import org.restlet.util.Series; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import org.restlet.ext.odata.internal.edm.TypeUtils; +import org.restlet.ext.odata.internal.FunctionContentHandler; +import org.restlet.ext.odata.internal.JsonContentFunctionHandler; +import org.restlet.ext.odata.internal.AtomContentFunctionHandler; +import org.restlet.ext.odata.batch.request.BatchRequest; +import org.restlet.ext.odata.batch.request.impl.BatchRequestImpl; <#list entityContainer.entities?sort as entitySet> -import ${entitySet.type.fullClassName}; +import ${entityClassPkg}.${entitySet.type.className}; + + +<#list schema.complexTypes? sort as ct> +import ${entityClassPkg}.${ct.className}; <#compress> @@ -56,12 +74,46 @@ import ${entitySet.type.fullClassName}; public class ${className} extends org.restlet.ext.odata.Service { - /** - * Constructor. - * - */ + FunctionContentHandler functionContentHandler; + + /** + * Constructor. + * + */ public ${className}() { super("${dataServiceUri}"); + this.functionContentHandler = new JsonContentFunctionHandler(); + <#if challengeScheme??> + super.setCredentials(new ChallengeResponse(${challengeScheme}, "${userName}", "${password}")); + + } + + <#if challengeScheme??> + public ${className}(String serviceUrl, String userName, String password) { + super(serviceUrl); + this.functionContentHandler = new JsonContentFunctionHandler(); + super.setCredentials(new ChallengeResponse(${challengeScheme}, userName, password)); + } + + + public ${className}(String serviceUrl, String userName, String password, ChallengeScheme cs) { + super(serviceUrl); + this.functionContentHandler = new JsonContentFunctionHandler(); + super.setCredentials(new ChallengeResponse(cs, userName, password)); + } + + <#if challengeScheme??> + public ${className}(String serviceUrl, String userName, String password, FunctionContentHandler functionContentHandler) { + super(serviceUrl); + this.functionContentHandler = functionContentHandler; + super.setCredentials(new ChallengeResponse(${challengeScheme}, userName, password)); + } + + + public ${className}(String serviceUrl, String userName, String password, ChallengeScheme cs, FunctionContentHandler functionContentHandler) { + super(serviceUrl); + this.functionContentHandler = functionContentHandler; + super.setCredentials(new ChallengeResponse(cs, userName, password)); } <#list entityContainer.entities as entitySet> @@ -71,10 +123,13 @@ public class ${className} extends org.restlet.ext.odata.Service { * * @param entity * The entity to add to the service. + * @return entity Type Object + * The newly created entity. + * * @throws Exception */ - public void addEntity(${type.fullClassName} entity) throws Exception { - <#if entityContainer.defaultEntityContainer>addEntity("/${entitySet.name}", entity);<#else>addEntity("/${entityContainer.name}.${entitySet.name}", entity); + public ${type.className} addEntity(${type.className} entity) throws Exception { + return <#if entityContainer.defaultEntityContainer>addEntity("/${entitySet.name}", entity);<#else>addEntity("/${entityContainer.name}.${entitySet.name}", entity); } /** @@ -84,8 +139,8 @@ public class ${className} extends org.restlet.ext.odata.Service { * The path to this entity relatively to the service URI. * @return A query object. */ - public Query<${type.fullClassName}> create${type.className}Query(String subpath) { - return createQuery(subpath, ${type.fullClassName}.class); + public Query<${type.className}> create${type.className}Query(String subpath) { + return createQuery(subpath, ${type.className}.class); } <#if (type.blob && type.blobValueRefProperty?? && type.blobValueRefProperty.name??)> @@ -96,7 +151,7 @@ public class ${className} extends org.restlet.ext.odata.Service { * The given media resource. * @return The binary representation of the given media resource. */ - public Representation getValue(${type.fullClassName} entity) throws ResourceException { + public Representation getValue(${type.className} entity) throws ResourceException { Reference ref = getValueRef(entity); if (ref != null) { ClientResource cr = createResource(ref); @@ -115,7 +170,7 @@ public class ${className} extends org.restlet.ext.odata.Service { * The requested media types of the representation. * @return The given media resource. */ - public Representation getValue(${type.fullClassName} entity, + public Representation getValue(${type.className} entity, List> acceptedMediaTypes) throws ResourceException { Reference ref = getValueRef(entity); @@ -137,7 +192,7 @@ public class ${className} extends org.restlet.ext.odata.Service { * The requested media type of the representation * @return The given media resource. */ - public Representation getValue(${type.fullClassName} entity, MediaType mediaType) + public Representation getValue(${type.className} entity, MediaType mediaType) throws ResourceException { Reference ref = getValueRef(entity); if (ref != null) { @@ -155,7 +210,7 @@ public class ${className} extends org.restlet.ext.odata.Service { * The media resource. * @return The reference of the binary representation of the given entity. */ - public Reference getValueRef(${type.fullClassName} entity) { + public Reference getValueRef(${type.className} entity) { if (entity != null) { return entity.get${type.blobValueRefProperty.name?cap_first}(); } @@ -172,7 +227,7 @@ public class ${className} extends org.restlet.ext.odata.Service { * The new representation. * @throws ResourceException */ - public void setValue(${type.fullClassName} entity, Representation blob) + public void setValue(${type.className} entity, Representation blob) throws ResourceException { Reference ref = entity.get${type.blobValueEditRefProperty.name?cap_first}(); @@ -182,6 +237,119 @@ public class ${className} extends org.restlet.ext.odata.Service { } } + + /** + * Returns the instance of BatchRequestImpl for batch requests. + * + * @return The reference of the binary representation of the given entity. + */ + public BatchRequest createBatchRequest(){ + return new BatchRequestImpl(this); + } + +<#list entityContainer.functionImports as functionImport> + <#if functionImport.complex> + public ${functionImport.javaReturnType} ${functionImport.name}( + <#assign i= functionImport.parameters?size> + <#list functionImport.parameters as parameter> + <#if i==1> + ${parameter.javaType} ${parameter.name} + <#else> + ${parameter.javaType} ${parameter.name}, + + <#assign i = i-1> + ) { + <#if functionImport.javaReturnType!="void"> + ${functionImport.javaReturnType} ${functionImport.name} = null; + + Series parameters = new Series(Parameter.class); + <#list functionImport.parameters as parameter> + Parameter param${parameter.name} = new Parameter(); + param${parameter.name}.setName("${parameter.name}"); + <#if parameter.type?starts_with("List") || parameter.type?starts_with("Collection")> + Gson gson = new GsonBuilder().serializeNulls().serializeSpecialFloatingPointValues().create(); + String jsonValue=gson.toJson(${parameter.name}); + param${parameter.name}.setValue(jsonValue); + <#else> + param${parameter.name}.setValue(${parameter.name}!=null?${parameter.name}.toString():null); + + parameters.add(param${parameter.name}); + + <#if functionImport.javaReturnType!="void"> + Representation representation = invokeComplex("${functionImport.name}", parameters); + ${functionImport.name} = (${functionImport.javaReturnType})this.functionContentHandler.parseResult(${functionImport.javaReturnType}.class, representation, "${functionImport.name}",null); + <#else> + invokeComplex("${functionImport.name}", parameters); + + <#if functionImport.javaReturnType!="void"> + return ${functionImport.name}; + + } + + + <#if functionImport.collection> + public ${functionImport.javaReturnType} ${functionImport.name}( + <#assign i= functionImport.parameters?size> + <#list functionImport.parameters as parameter> + <#if i==1> + ${parameter.javaType} ${parameter.name} + <#else> + ${parameter.javaType} ${parameter.name}, + + <#assign i = i-1> + ) { + <#if functionImport.javaReturnType!="void"> + ${functionImport.javaReturnType} ${functionImport.name} = new ArrayList<${functionImport.returnType}>(); + + Series parameters = new Series(Parameter.class); + <#list functionImport.parameters as parameter> + Parameter param${parameter.name} = new Parameter(); + param${parameter.name}.setName("${parameter.name}"); + <#if parameter.type?starts_with("List") || parameter.type?starts_with("Collection")> + Gson gson = new GsonBuilder().serializeNulls().serializeSpecialFloatingPointValues().create(); + String jsonValue=gson.toJson(${parameter.name}); + param${parameter.name}.setValue(jsonValue); + <#else> + param${parameter.name}.setValue(${parameter.name}!=null?${parameter.name}.toString():null); + + parameters.add(param${parameter.name}); + + <#if functionImport.javaReturnType!="void"> + Representation representation = invokeComplex("${functionImport.name}", parameters); + ${functionImport.name} = (${functionImport.javaReturnType})this.functionContentHandler.parseResult(${functionImport.returnType}.class, representation, "${functionImport.name}", ${functionImport.name}); + <#else> + invokeComplex("${functionImport.name}", parameters); + + <#if functionImport.javaReturnType!="void"> + return ${functionImport.name}; + + } + + + <#if functionImport.simple> + public ${functionImport.javaReturnType} ${functionImport.name} ( + <#assign i= functionImport.parameters?size> + <#list functionImport.parameters as parameter> + <#if i==1> + ${parameter.javaType} ${parameter.name} + <#else> + ${parameter.javaType} ${parameter.name}, + + <#assign i = i-1> + ) throws ResourceException, Exception{ + ${functionImport.javaReturnType} ${functionImport.name} = null; + Series parameters = new Series(Parameter.class); + <#list functionImport.parameters as parameter> + Parameter param${parameter.name} = new Parameter(); + param${parameter.name}.setName("${parameter.name}"); + param${parameter.name}.setValue(${parameter.name}!=null?${parameter.name}.toString():null); + parameters.add(param${parameter.name}); + + String stringValue = invokeSimple("${functionImport.name}", parameters); + ${functionImport.name} = (${functionImport.javaReturnType})TypeUtils.convert(${functionImport.javaReturnType}.class, stringValue); + return ${functionImport.name}; + } + } \ No newline at end of file diff --git a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/streaming/StreamReference.java b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/streaming/StreamReference.java new file mode 100644 index 0000000000..1f8cfc308d --- /dev/null +++ b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/streaming/StreamReference.java @@ -0,0 +1,177 @@ +package org.restlet.ext.odata.streaming; + +import java.io.IOException; +import java.io.InputStream; +import java.util.logging.Level; + +import org.restlet.Context; +import org.restlet.data.Reference; +import org.restlet.ext.odata.Service; +import org.restlet.representation.Representation; +import org.restlet.resource.ClientResource; + +/** + * This class stores the Stream data reference and its contentType.One can read, + * Create and update stream data by using this class.
+ *
+ * + * The {@link InputStream} to be read as Media Link Entry(MLE)/Stream from the + * server is fetched lazily using a call to the {@link #getInputStream(Service)} + * method. Please note that it is essential to pass the {@link Service} to the + * method as the service instance holds the authenticated credentials that needs + * to be passed as part of this client request.
+ *
+ * + * DO NOT use the {@link #getInputStream()} method for lazy fetching a + * stream; this method is used internally by the framework while saving the + * stream.When saving a MLE, please use the {@link #setInputStream(InputStream)} + * method to set the reference to the {@link InputStream} that you want to + * persist on the server.
+ *
+ * + * NOTE + *
    + *
  1. If you need to use blocking {@link InputStream} continous inputstream as + * in case of protocol buffers; please use the org.restlet.ext.net + * connector by placing the org.restlet.ext.net.jar file in the + * classpath.
  2. + *
  3. If you need to use non-blocking I/O to read the stream using buffered + * data reader as in the case you are reading files/documents; please use the + * org.restlet.ext.httpclient connector by placing the + * org.restlet.ext.httpclient.jar file and it's dependencies in the + * classpath.
  4. + *
  5. Note that at any point of time the client can use only 1 connector and + * they have to be mutually exclusive.
  6. + *
+ */ +public class StreamReference extends Reference { + + /** The input stream. */ + private InputStream inputStream; + + /** The content type. */ + private String contentType; + + /** isUpdateStreamData needs to be set to true for Stream Update */ + private boolean isUpdateStreamData; + + /** + * Instantiates a new stream reference. This method is used to create + * StreamReference with the URL. + * + * @param baseRef + * the base ref + * @param uriRef + * the uri ref + */ + public StreamReference(Reference baseRef, String uriRef) { + super(baseRef, uriRef); + } + + /** + * Instantiates a new stream reference. Use this to instantiates stream + * reference for doing create. + * + * @param contentType + * the content type + * @param inputStream + * the input stream + * @param isCreate + * the is create + */ + public StreamReference(String contentType, InputStream inputStream) { + this.contentType = contentType; + this.inputStream = inputStream; + } + + /** + * @return the contentType + */ + public String getContentType() { + return contentType; + } + + /** + * @param contentType + * the contentType to set + */ + public void setContentType(String contentType) { + this.contentType = contentType; + } + + /** + * This method returns the stream that provides the handle to the data being + * pushed back from the server.
+ *
+ * The client program should use the org.restlet.ext.net connector + * when the client depends on the entire inputstream being available for + * processing say in case of processing Google Protocol Buffer streams.
+ *
+ * + * The client program should use the org.restlet.ext.httpclient + * connector when the client is downloading file or document based streams + * that don't need to be available for client program to process as + * continuous input stream. + * + * @param service + * the service + * @return the inputStream + * @throws IOException + * Signals that an I/O exception has occurred. + */ + public InputStream getInputStream(Service service) throws IOException { + + try { + ClientResource clientResource = service.createResource(this); + Representation representation = clientResource.get(); + if (representation != null) { + // Provide a stream based on what is returned by the server + this.inputStream = representation.getStream(); + } + } catch (IOException ioException) { + Context.getCurrentLogger().log( + Level.WARNING, + "Exception while retrieving the non blocking streaming data: " + + ioException.getMessage(), ioException); + throw ioException; + } + return this.inputStream; + } + + /** + * @param inputStream + * the inputStream to set + */ + public void setInputStream(InputStream inputStream) { + this.inputStream = inputStream; + } + + /** + * Gets the input stream. + * + * @return the input stream + */ + public InputStream getInputStream() { + return inputStream; + } + + /** + * Checks if is update stream data. + * + * @return the isUpdateStreamData + */ + public boolean isUpdateStreamData() { + return isUpdateStreamData; + } + + /** + * Sets the update stream data. + * + * @param isUpdateStreamData + * the isUpdateStreamData to set + */ + public void setUpdateStreamData(boolean isUpdateStreamData) { + this.isUpdateStreamData = isUpdateStreamData; + } + +} diff --git a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/validation/annotation/JavaReservedKeyWord.java b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/validation/annotation/JavaReservedKeyWord.java new file mode 100644 index 0000000000..a4a67f05fa --- /dev/null +++ b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/validation/annotation/JavaReservedKeyWord.java @@ -0,0 +1,21 @@ + +package org.restlet.ext.odata.validation.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicates that the annotated property is java reserved key word + * + * @author Akshay + */ + +//Indicates that annotations with this type are to be retained by the VM so they can be read reflectively at run-time +@Retention(RetentionPolicy.RUNTIME) +//Indicates that this annotation type can be used to annotate only field +@Target(ElementType.FIELD) +public @interface JavaReservedKeyWord { + +} diff --git a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/validation/annotation/NotNull.java b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/validation/annotation/NotNull.java new file mode 100644 index 0000000000..e92757813e --- /dev/null +++ b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/validation/annotation/NotNull.java @@ -0,0 +1,21 @@ + +package org.restlet.ext.odata.validation.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicates that the annotated property can be null. + * + * @author Shantanu + */ + +//Indicates that annotations with this type are to be retained by the VM so they can be read reflectively at run-time +@Retention(RetentionPolicy.RUNTIME) +//Indicates that this annotation type can be used to annotate only field +@Target(ElementType.FIELD) +public @interface NotNull { + +} diff --git a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/validation/annotation/PrimaryKey.java b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/validation/annotation/PrimaryKey.java new file mode 100644 index 0000000000..ef952e428b --- /dev/null +++ b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/validation/annotation/PrimaryKey.java @@ -0,0 +1,21 @@ + +package org.restlet.ext.odata.validation.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicates that the annotated property is Primary Key or not. + * + * @author Shantanu + */ + +//Indicates that annotations with this type are to be retained by the VM so they can be read reflectively at run-time +@Retention(RetentionPolicy.RUNTIME) +//Indicates that this annotation type can be used to annotate only field +@Target(ElementType.FIELD) +public @interface PrimaryKey { + +} diff --git a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/validation/annotation/SystemGenerated.java b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/validation/annotation/SystemGenerated.java new file mode 100644 index 0000000000..779c4ce798 --- /dev/null +++ b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/validation/annotation/SystemGenerated.java @@ -0,0 +1,21 @@ + +package org.restlet.ext.odata.validation.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicates that the annotated property is System Generated or not. + * + * @author Shantanu + */ + +//Indicates that annotations with this type are to be retained by the VM so they can be read reflectively at run-time +@Retention(RetentionPolicy.RUNTIME) +//Indicates that this annotation type can be used to annotate only field +@Target(ElementType.FIELD) +public @interface SystemGenerated { + +} diff --git a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/xml/AtomFeedHandler.java b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/xml/AtomFeedHandler.java new file mode 100644 index 0000000000..e76da1f806 --- /dev/null +++ b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/xml/AtomFeedHandler.java @@ -0,0 +1,874 @@ +package org.restlet.ext.odata.xml; + +import java.io.Reader; +import java.io.StringWriter; +import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.sql.Date; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import javax.xml.namespace.QName; +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.events.Attribute; +import javax.xml.stream.events.StartElement; +import javax.xml.stream.events.XMLEvent; + +import org.restlet.Context; +import org.restlet.data.Language; +import org.restlet.data.MediaType; +import org.restlet.data.Reference; +import org.restlet.ext.atom.Content; +import org.restlet.ext.atom.Entry; +import org.restlet.ext.atom.Feed; +import org.restlet.ext.atom.Link; +import org.restlet.ext.atom.Person; +import org.restlet.ext.atom.Relation; +import org.restlet.ext.odata.internal.edm.EntitySet; +import org.restlet.ext.odata.internal.edm.EntityType; +import org.restlet.ext.odata.internal.edm.Mapping; +import org.restlet.ext.odata.internal.edm.Metadata; +import org.restlet.ext.odata.internal.edm.TypeUtils; +import org.restlet.ext.odata.internal.reflect.ReflectUtils; +import org.restlet.ext.odata.streaming.StreamReference; +import org.restlet.ext.xml.format.FormatParser; +import org.restlet.ext.xml.format.XmlFormatParser; + +/** + * The Class AtomFeedHandler which parses the AtomFeed using STAX Event Iterator model. + * + * @param the generic type + * + * @author Onkar Dhuri + */ +public class AtomFeedHandler extends XmlFormatParser implements + FormatParser { + + /** The metadata. */ + protected Metadata metadata; + + /** The entity set name. */ + protected String entitySetName; + + /** The entity type. */ + private EntityType entityType; + + /** The entity class. */ + private Class entityClass; + + /** The entities. */ + private List entities; + + /** The references from the entry to Web resources. */ + private volatile List links; + + /** The feed. */ + private Feed feed; + + /** The baseURL. */ + private String baseURL; + + /** + * Gets the entities. + * + * @return the entities + */ + public List getEntities() { + return entities; + } + + /** + * Gets the feed. + * + * @return the feed + */ + public Feed getFeed(){ + if(feed == null){ + feed = new Feed(); + } + return feed; + } + + /** + * Sets the feed. + * + * @param feed the new feed + */ + public void setFeed(Feed feed){ + this.feed = feed; + } + + /** + * Returns the references from the entry to Web resources. + * + * @return The references from the entry to Web resources. + */ + public List getLinks() { + // Lazy initialization with double-check. + List l = this.links; + if (l == null) { + synchronized (this) { + l = this.links; + if (l == null) { + this.links = l = new ArrayList(); + } + } + } + return l; + } + + /** + * Instantiates a new atom feed handler. + * + * @param entitySetName the entity set name + * @param entityType the entity type + * @param entityClass the entity class + */ + public AtomFeedHandler(String entitySetName, EntityType entityType, Class entityClass) { + this.entitySetName = entitySetName; + this.entityType = entityType; + this.entityClass = entityClass; + this.entities = new ArrayList(); + } + + /** + * Instantiates a new atom feed handler. + * + * @param entitySetName the entity set name + * @param entityType the entity type + * @param entityClass the entity class + * @param metadata the metadata + */ + public AtomFeedHandler(String entitySetName, EntityType entityType, Class entityClass, Metadata metadata) { + this.entitySetName = entitySetName; + this.entityType = entityType; + this.entityClass = entityClass; + this.entities = new ArrayList(); + this.metadata = metadata; + } + + /* (non-Javadoc) + * @see org.restlet.ext.xml.format.FormatParser#parse(java.io.Reader) + */ + public Feed parse(Reader reader) { + return parseFeed(reader, getEntitySet()); + } + + /** + * Parses the feed. + * + * @param reader the reader + * @param entitySet the entity set + * @return the feed + */ + public Feed parseFeed(Reader reader, EntitySet entitySet) { + try { + + XMLInputFactory factory = XMLInputFactory.newInstance(); + XMLEventReader eventReader = factory.createXMLEventReader(reader); + + while (eventReader.hasNext()) { + XMLEvent event; + event = eventReader.nextEvent(); + + if (isStartElement(event, ATOM_ENTRY)) { + @SuppressWarnings("unchecked") + // create instance of entity, parse it and add it to list of entities + T entity = (T) entityClass.newInstance(); + this.parseEntry(eventReader, event.asStartElement(), entitySet, entity); + this.entities.add(entity); + + } else if (isStartElement(event, ATOM_LINK)) { + if ("next".equals(event.asStartElement() + .getAttributeByName(new QName("rel")).getValue())) { + this.getFeed().setBaseReference(event.asStartElement() + .getAttributeByName(new QName("href")) + .getValue()); + } + parseAtomFeedLinks(event); + } else if (isStartElement(event, ATOM_FEED)) { + Attribute attributeByName = event.asStartElement().getAttributeByName(XML_BASE); + if(attributeByName!=null){ + baseURL=attributeByName.getValue(); + } + } else if (isEndElement(event, ATOM_FEED)) { + // return from a sub feed, if we went down the hierarchy + break; + } + + } + } catch (XMLStreamException e) { + Context.getCurrentLogger().warning( + "Cannot parse the feed due to: " + e.getMessage()); + } catch (InstantiationException e) { + Context.getCurrentLogger().warning( + "Cannot parse the feed due to: " + e.getMessage()); + } catch (IllegalAccessException e) { + Context.getCurrentLogger().warning( + "Cannot parse the feed due to: " + e.getMessage()); + } + + return this.getFeed(); + + } + + /** + * Method to parse atom links. + * @param event + */ + private void parseAtomFeedLinks(XMLEvent event) { + Link link = new Link(); + link.setHref(new Reference(event.asStartElement() + .getAttributeByName(new QName("href")) + .getValue())); + link.setRel(Relation.valueOf(event.asStartElement() + .getAttributeByName(new QName("rel")) + .getValue())); + if(null != event.asStartElement() + .getAttributeByName(new QName("type"))){ + String type = event.asStartElement() + .getAttributeByName(new QName("type")) + .getValue(); + if (type != null && type.length() > 0) { + link.setType(new MediaType(type)); + } + } + + if(null != event.asStartElement() + .getAttributeByName(new QName("hreflang"))){ + link.setHrefLang(new Language(event.asStartElement() + .getAttributeByName(new QName("hreflang")) + .getValue())); + } + + if(null != event.asStartElement() + .getAttributeByName(new QName("title"))){ + link.setTitle(event.asStartElement() + .getAttributeByName(new QName("title")) + .getValue()); + } + + if(null != event.asStartElement() + .getAttributeByName(new QName("length"))){ + final String attr = event.asStartElement() + .getAttributeByName(new QName("length")) + .getValue(); + link.setLength((attr == null) ? -1L : Long.parseLong(attr)); + } + + + // Glean the content + Content currentContent = new Content(); + // Content available inline + //initiateInlineMixedContent(); + link.setContent(currentContent); + + + getFeed().getLinks().add(link); + } + + + + /** + * Parses all properties of an entity. + * + * @param the generic type + * @param reader the reader + * @param propertiesElement the properties element + * @param entity the entity + * @return the t + */ + public static T parseProperties( + XMLEventReader reader, StartElement propertiesElement, T entity) { + + try { + String propertyName = null; + while (reader.hasNext()) { + XMLEvent event = reader.nextEvent(); + + if (event.isEndElement() + && event.asEndElement().getName() + .equals(propertiesElement.getName())) { + return entity; + } + + if (event.isStartElement() + && event.asStartElement().getName().getNamespaceURI() + .equals(NS_DATASERVICES)) { + + String name = event.asStartElement().getName().getLocalPart(); + propertyName = ReflectUtils.normalize(name); + //Check if that property is java reserved key word. If so then prefix it with '_' + if(ReflectUtils.isReservedWord(propertyName)){ + propertyName="_"+propertyName; + } + parsePropertiesByType(reader, entity, propertyName, event); + } + } + } catch (Exception e) { + Context.getCurrentLogger().warning( + "Cannot parse the properties due to: " + e.getMessage()); + throw new RuntimeException(); + } + return entity; + } + + /** Method to parse properties of different types. + * @param reader + * @param entity + * @param propertyName + * @param event + * @throws XMLStreamException + * @throws Exception + */ + @SuppressWarnings("unchecked") + private static void parsePropertiesByType(XMLEventReader reader, T entity, String propertyName, XMLEvent event) { + try { + Object value = null; + Attribute typeAttribute = event.asStartElement() + .getAttributeByName(M_TYPE); + Attribute nullAttribute = event.asStartElement() + .getAttributeByName(M_NULL); + boolean isNull = nullAttribute != null + && "true".equals(nullAttribute.getValue()); + if (typeAttribute == null) { // Simple String + value = reader.getElementText(); + } else if (typeAttribute.getValue().toLowerCase().startsWith("edm") + && !isNull) { // EDM Type + value = TypeUtils.fromEdm(reader.getElementText(), + typeAttribute.getValue()); + } else if (typeAttribute.getValue().toLowerCase() + .startsWith("collection")) {// collection type + Object o = ReflectUtils.getPropertyObject(entity, propertyName); + // Delegate the collection handling to respective handler. + CollectionPropertyHandler.parse(reader, o, + event.asStartElement(), entity); + } else if (!isNull) {// complex type + // get or create the property instance + Object o = ReflectUtils.getPropertyObject(entity, propertyName); + // populate the object + parseProperties(reader, event.asStartElement(), (T) o); + // set it back to parent entity + ReflectUtils.invokeSetter(entity, propertyName, o); + } + if (value != null) { + ReflectUtils.invokeSetter(entity, propertyName, value); + } + } catch (XMLStreamException e) { + Context.getCurrentLogger().warning( + "Cannot parse the property due to: " + e.getMessage()); + } catch (Exception e) { + Context.getCurrentLogger().warning( + "Cannot parse the property due to: " + e.getMessage()); + } + } + + /** + * Parses the ds atom entry. + * + * @param entityType the entity type + * @param reader the reader + * @param event the event + * @param entity the entity + */ + private void parseDSAtomEntry(EntityType entityType, XMLEventReader reader, XMLEvent event, T entity) { + // as end element is not included in parseProperties, we need a wrapper method around it to handle it. + AtomFeedHandler.parseProperties(reader, event.asStartElement(), entity); + } + + + /** + * API will provide the Text for the given element + * + * @param reader - XMLEventReader + * @param element - StartElement + * @return String + */ + public static String innerText(XMLEventReader reader, StartElement element) { + try { + StringWriter sw = new StringWriter(); + while (reader.hasNext()) { + + XMLEvent event = reader.nextEvent(); + if (event.isEndElement() + && event.asEndElement().getName() + .equals(element.getName())) { + + return sw.toString(); + } else { + sw.append(event.asCharacters().getData()); + } + + } + } catch (XMLStreamException e) { + Context.getCurrentLogger().warning( + "Cannot parse the inner content due to: " + e.getMessage()); + } + throw new RuntimeException(); + } + + + + /** + * Gets the entity set. + * + * @return the entity set + */ + private EntitySet getEntitySet() { + EntitySet entitySet = new EntitySet(entitySetName); + entitySet.setType(entityType); + + return entitySet; + } + + /** + * Parses the entry. + * + * @param reader the reader + * @param entryElement the entry element + * @param entitySet the entity set + * @param entity the entity + * @return the t + */ + @SuppressWarnings("rawtypes") + private T parseEntry(XMLEventReader reader, + StartElement entryElement, EntitySet entitySet, T entity) { + + String id = null; + String title = null; + String summary = null; + String updated = null; + String contentType = null; + Person p = null; + String relativeURL=null; + //read the base URL form Feed/Entry + if(null==baseURL){ + Attribute attributeByName = entryElement.getAttributeByName(XML_BASE); + if(attributeByName!=null){ + baseURL=attributeByName.getValue(); + } + } + + + Entry rt = new Entry(); + + while (reader.hasNext()) { + try { + XMLEvent event; + event = reader.nextEvent(); + if (event.isEndElement() + && event.asEndElement().getName() + .equals(entryElement.getName())) { + rt.setId(id); // http://localhost:8810/Oneoff01.svc/Comment(1) + rt.setTitle(title); + rt.setSummary(summary); + rt.setUpdated(Date.valueOf(updated.split("T")[0])); + this.getFeed().getEntries().add(rt); + if(p!=null){ + //set author details in the entity. + setDetailsOfPerson(entity, p, null); // third parameter would be null if we don't have contributor object. + } + return entity; + } + + if (isStartElement(event, ATOM_ID)) { + id = reader.getElementText(); + } else if (isStartElement(event, ATOM_TITLE)) { + title = reader.getElementText(); + } else if (isStartElement(event, ATOM_SUMMARY)) { + summary = reader.getElementText(); + } else if (isStartElement(event, ATOM_UPDATED)) { + updated = reader.getElementText(); + } else if (isStartElement(event, ATOM_LINK)) { + Link link = parseAtomLink(reader, event.asStartElement(), + entitySet, entity); + rt.getLinks().add(link); + } else if (isStartElement(event, M_PROPERTIES)) { + parseDSAtomEntry(entitySet.getType(), reader, event, entity); + /*} else if (isStartElement(event, M_ACTION)) { + AtomFunction function = parseAtomFunction(reader, + event.asStartElement()); + actions.put(function.getFQFunctionName(), metadata + .findFunctionImport(function.title, + entitySet.getType(), FunctionKind.Action)); + } else if (isStartElement(event, M_FUNCTION)) { + AtomFunction function = parseAtomFunction(reader, + event.asStartElement()); + functions.put(function.getFQFunctionName(), metadata + .findFunctionImport(function.title, + entitySet.getType(), FunctionKind.Function));*/ + } else if(isStartElement(event, ATOM_AUTHOR)){ + // handle Author tag completely and create person object. + p = new Person(); + parseAuthor(reader, p, event); + } else if (isStartElement(event, ATOM_CONTENT)) { + contentType = getAttributeValueIfExists(event.asStartElement(), + "type"); + relativeURL = getAttributeValueIfExists(event.asStartElement(), + "src"); + if (MediaType.APPLICATION_XML.getName().equals(contentType)) { + StartElement contentElement = event.asStartElement(); + StartElement valueElement = null; + while (reader.hasNext()) { + // handle content in separate handlers + XMLEvent event2; + try { + event2 = reader.nextEvent(); + if (valueElement == null && event2.isStartElement()) { + valueElement = event2.asStartElement(); + if (isStartElement(event2, M_PROPERTIES)) { + this.parseDSAtomEntry(entitySet.getType(), reader, event2, entity); + } else { + // TODO: Onkar : Set Basic content by implementing innerText method later + //BasicAtomEntry bae = new BasicAtomEntry(); + Entry bae = new Entry(); + //bae.content = innerText(reader, event2.asStartElement()); + rt = bae; + } + } + if (event2.isEndElement() + && event2.asEndElement().getName() + .equals(contentElement.getName())) { + break; + } + } catch (XMLStreamException e) { + Context.getCurrentLogger().warning( + "Cannot parse the entry due to: " + e.getMessage()); + } + } + } else { + if(entityType.isBlob()){ + for (Field field : entity.getClass() + .getDeclaredFields()) { + Class type = field.getType(); + if(type.getName().contains("StreamReference")){ + Reference baseReference = new Reference(baseURL); + StreamReference streamReference = new StreamReference(baseReference,relativeURL); + streamReference.setContentType(contentType); + ReflectUtils.invokeSetter(entity, + ReflectUtils.normalize(field.getName()), streamReference); + break; + } + } + } + Entry e = new Entry(); + // TODO: Onkar : Set Basic content by implementing innerText method later + //e.setContent(innerText(reader, event.asStartElement())); + rt = e; + } + } else if(event.toString() != null && event.toString().isEmpty() && event.toString().trim().isEmpty()){ + continue; + } else { // Handle Custom feeds where some properties are outside content tag. ie. not in m:properties + if(event.isStartElement()){ + List mappings = metadata.getMappings(); + Entry e = new Entry(); + StartElement element = event.asStartElement(); + String parentTag = element.getName().getLocalPart(); + + // Iterate through inline attributes and set respective property for the entity. + // Eg: + String nsPrefix = element.getName().getPrefix(); + for (Iterator iterator1 = element.getAttributes(); iterator1.hasNext();) { + Attribute attribute = (Attribute)iterator1.next(); + String attributeValue = getAttributeValueIfExists(element, attribute.getName()); + String attibuteTag = parentTag+"/@"+attribute.getName().getLocalPart(); + for (Iterator iterator = mappings.iterator(); iterator.hasNext();) { + Mapping mapping = (Mapping) iterator.next(); + if(null != mapping.getNsPrefix() && mapping.getNsPrefix().equalsIgnoreCase(nsPrefix)){ + if(attributeValue!= null & mapping.getValuePath().equalsIgnoreCase(attibuteTag)){ + String propertyName = ReflectUtils.normalize(mapping.getPropertyPath()); + ReflectUtils.invokeSetter(entity, propertyName, attributeValue); + break; + } + } + } + } + + + StartElement valueElement = null; + String propertyName = null; + while(reader.hasNext()){ + + XMLEvent event2; + + try { + // iterate all the sub-elements under parent tag with + event2 = reader.nextEvent(); + if (event2.isEndElement() + && event2.asEndElement().getName() + .equals(element.getName())) { + break; + } + if (valueElement == null && event2.isStartElement()) { + valueElement = event2.asStartElement(); + // Iterate through sub-paths and set respective property for the entity. + // Eg:Cafe corp. + String innerTag = parentTag+"/"+valueElement.getName().getLocalPart(); + for (Iterator iterator = mappings.iterator(); iterator.hasNext();) { + Mapping mapping = (Mapping) iterator.next(); + if(mapping.getValuePath().equalsIgnoreCase(innerTag)){ + + //TODO: Abhijeet : Extract this into method and refer it in parseProperties + propertyName = ReflectUtils.normalize(mapping.getPropertyPath()); + parsePropertiesByType(reader, entity, propertyName, event2); + break; // exit for loop if mapping is present and addressed + + } + } + } + }catch (XMLStreamException e1) { + Context.getCurrentLogger().warning( + "Cannot parse the entry due to: " + e1.getMessage()); + } catch (Exception e1) { + Context.getCurrentLogger().warning( + "Cannot parse the entry due to: " + e1.getMessage()); + } + } + rt = e; + } + } + } catch (XMLStreamException e1) { + Context.getCurrentLogger().warning( + "Cannot parse the entry due to: " + e1.getMessage()); + } catch (Exception e1) { + Context.getCurrentLogger().warning( + "Cannot parse the entry due to: " + e1.getMessage()); + } + + } + throw new RuntimeException(); + } + + /** + * @param reader + * @param p + * @param event + */ + private void parseAuthor(XMLEventReader reader, Person p, XMLEvent event) { + StartElement contentElement = event.asStartElement(); + StartElement valueElement = null; + while (reader.hasNext()) { + // handle content in separate handlers + XMLEvent event2; + try { + event2 = reader.nextEvent(); + if (valueElement == null && event2.isStartElement()) { + valueElement = event2.asStartElement(); + if (isStartElement(event2, ATOM_NAME)) { + String name = reader.getElementText(); + if(!name.isEmpty()){ + p.setName(name); + } + }else if (isStartElement(event2, ATOM_EMAIL)) { + String email = reader.getElementText(); + if(!email.isEmpty()){ + p.setEmail(email); + } + } + } + if (event2.isEndElement() + && event2.asEndElement().getName() + .equals(contentElement.getName())) { + break; + } + }catch (XMLStreamException e) { + Context.getCurrentLogger().warning( + "Cannot parse the author due to: " + e.getMessage()); + } + } + } + + /** + * Method to set atom mappings to corresponding entity. + * @param entity + * @param author + * @param contributor + */ + private void setDetailsOfPerson(T entity, Person author, Person contributor) { + // Handle Atom mapped values. + for (Mapping m : metadata.getMappings()) { + if (entityType != null && entityType.equals(m.getType()) + && m.getNsUri() == null && m.getNsPrefix() == null) { + + Object value = null; + if ("SyndicationAuthorEmail".equals(m.getValuePath())) { + value = (author != null) ? author.getEmail() : null; + } else if ("SyndicationAuthorName".equals(m.getValuePath())) { + value = (author != null) ? author.getName() : null; + } else if ("SyndicationAuthorUri".equals(m.getValuePath())) { + value = (author != null) ? author.getUri().toString() + : null; + } else if ("SyndicationContributorEmail".equals(m + .getValuePath())) { + value = (contributor != null) ? contributor.getEmail() + : null; + } else if ("SyndicationContributorName" + .equals(m.getValuePath())) { + value = (contributor != null) ? contributor.getName() + : null; + } else if ("SyndicationContributorUri".equals(m.getValuePath())) { + value = (contributor != null) ? contributor.getUri() + .toString() : null; + } /*else if ("SyndicationPublished".equals(m.getValuePath())) { + value = entry.getPublished(); + } else if ("SyndicationRights".equals(m.getValuePath())) { + value = (entry.getRights() != null) ? entry.getRights() + .getContent() : null; + } else if ("SyndicationSummary".equals(m.getValuePath())) { + value = entry.getSummary(); + } else if ("SyndicationTitle".equals(m.getValuePath())) { + value = (entry.getTitle() != null) ? entry.getTitle() + .getContent() : null; + } else if ("SyndicationUpdated".equals(m.getValuePath())) { + value = entry.getUpdated(); + }*/ + + try { + if (value != null) { + ReflectUtils.invokeSetter(entity, m.getPropertyPath(), + value); + } + } catch (Exception e) { + Context.getCurrentLogger().warning( + "Cannot parse the content due to: " + e.getMessage()); + } + } + } + + } + + /** + * Parses the atom link. + * + * @param reader the reader + * @param linkElement the link element + * @param entitySet the entity set + * @param entity the entity + * @return the link + */ + private Link parseAtomLink(XMLEventReader reader, StartElement linkElement, + EntitySet entitySet, T entity) { + + try { + Link rt = new Link(); + rt.setRel(Relation.valueOf(getAttributeValueIfExists(linkElement, + "rel"))); + rt.setType(MediaType.valueOf(getAttributeValueIfExists(linkElement, + "type"))); + rt.setTitle(getAttributeValueIfExists(linkElement, "title")); + rt.setHref(new Reference(getAttributeValueIfExists(linkElement, + "href"))); + //boolean inlineContent = false; + + // expected cases: + // 1. - no inlined content, i.e. deferred + // 2. - inlined content but null entity or empty feed + // 3. ... - inlined content with 1 or more items + // 4. .. - inlined content 1 an item + + while (reader.hasNext()) { + XMLEvent event; + event = reader.nextEvent(); + + if (event.isEndElement() + && event.asEndElement().getName() + .equals(linkElement.getName())) { + break; + } else if (isStartElement(event, XmlFormatParser.M_INLINE)) { + //inlineContent = true; // may be null content. + } else if (isStartElement(event, ATOM_FEED)) { + //skip all tags until we encounter first entry element to exclude link and other tags. + // Fix for edit link tag to prevent pre-matured exit of parent link iteration. + while( reader.hasNext()){ + event = reader.nextEvent(); + if(isStartElement(event,ATOM_ENTRY)){ + String propertyName = rt.getHref().getLastSegment(); + // create a property object + Object o = ReflectUtils.getPropertyObject(entity, propertyName); + parseInlineEntities(reader, entitySet, entity, event, propertyName, o); + // This break is added to not handle additional inline entities here. Those entries shall be handled in next else-if block. + break; + } else if(event.isEndElement() && event.asEndElement().getName().equals(ATOM_FEED.getLocalPart())){ + break; + } + } + } else if (isStartElement(event, ATOM_ENTRY)) { //handle the inline entity + String propertyName = rt.getHref().getLastSegment(); + // create a property object + Object o = ReflectUtils.getPropertyObject(entity, propertyName); + parseInlineEntities(reader, entitySet, entity, event, propertyName, o); + } + } + return rt; + } catch (XMLStreamException e) { + Context.getCurrentLogger().warning( + "Cannot parse the atom link due to: " + e.getMessage()); + } catch (Exception e) { + Context.getCurrentLogger().warning( + "Cannot parse the atom link due to: " + e.getMessage()); + } + return null; + } + + /** + * Method to parse Inline entities. + * @param reader + * @param entitySet + * @param entity + * @param event + * @param propertyName + * @param o + * @throws NoSuchFieldException + * @throws InstantiationException + * @throws IllegalAccessException + * @throws Exception + */ + @SuppressWarnings(value = {"unchecked", "rawtypes" }) + private void parseInlineEntities(XMLEventReader reader, EntitySet entitySet, T entity, XMLEvent event, + String propertyName, Object o) { + try { + if (o instanceof List) { // Collection of complex i.e. one to many association + Field field = entity.getClass().getDeclaredField(ReflectUtils.normalize(propertyName)); + // String currentMType = startElement.getAttributeByName(M_TYPE).getValue(); + // get the parameterize type using reflection + if (field.getGenericType() instanceof ParameterizedType) { + // determine what type of collection it is + ParameterizedType listType = (ParameterizedType) field.getGenericType(); + Class listClass = (Class) listType.getActualTypeArguments()[0]; + // Create new Item Instance + Object obj; + obj = listClass.newInstance(); + + // create a new instance and populate the properties + this.parseEntry(reader, event.asStartElement(), entitySet, (T) obj); + ((List) o).add(obj); + } + + } else { // complex object i.e. embedded object in parent entity + // populate the object + this.parseEntry(reader, event.asStartElement(), entitySet, (T) o); + // set it back to parent entity + ReflectUtils.invokeSetter(entity, ReflectUtils.normalize(propertyName), o); + } + } catch (InstantiationException e) { + Context.getCurrentLogger().warning( + "Cannot parse the inline entities due to: " + e.getMessage()); + } catch (IllegalAccessException e) { + Context.getCurrentLogger().warning( + "Cannot parse the inline entities due to: " + e.getMessage()); + } catch (SecurityException e) { + Context.getCurrentLogger().warning( + "Cannot parse the inline entities due to: " + e.getMessage()); + } catch (NoSuchFieldException e) { + Context.getCurrentLogger().warning( + "Cannot parse the inline entities due to: " + e.getMessage()); + } catch (Exception e) { + Context.getCurrentLogger().warning( + "Cannot parse the inline entities due to: " + e.getMessage()); + } + } + +} diff --git a/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/xml/CollectionPropertyHandler.java b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/xml/CollectionPropertyHandler.java new file mode 100644 index 0000000000..a19c3df813 --- /dev/null +++ b/modules/org.restlet.ext.odata/src/org/restlet/ext/odata/xml/CollectionPropertyHandler.java @@ -0,0 +1,105 @@ +package org.restlet.ext.odata.xml; + +import static org.restlet.ext.xml.format.XmlFormatParser.DATASERVICES_ELEMENT; +import static org.restlet.ext.xml.format.XmlFormatParser.M_TYPE; +import static org.restlet.ext.xml.format.XmlFormatParser.NS_DATASERVICES; + +import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.util.List; + +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.events.StartElement; +import javax.xml.stream.events.XMLEvent; + +import org.restlet.Context; +import org.restlet.ext.odata.internal.edm.TypeUtils; + +/** + * The Class CollectionPropertyHandler which handles all type of collections. + * + * @author Onkar Dhuri + */ +public class CollectionPropertyHandler { + + /** + * Parses the collection of either simple type or complex type + * + * @param the generic type + * @param reader the reader + * @param entity the entity + * @param startElement the start element + * @param parentEntity the parent entity + * @return the t + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static T parse(XMLEventReader reader, T entity, + StartElement startElement, T parentEntity) { + + try { + Object obj = null; + String collectionType; + while (reader.hasNext()) { + XMLEvent event = reader.nextEvent(); + + if (event.isEndElement() + && event.asEndElement().getName() + .equals(startElement.getName())) { + return entity; + } + + if (event.isStartElement() + && event.asStartElement().getName().getNamespaceURI() + .equals(NS_DATASERVICES) + && event.asStartElement().getName() + .equals(DATASERVICES_ELEMENT)) { + if (entity instanceof List) { // check first if it is type of List + Field field = parentEntity.getClass().getDeclaredField( + startElement.getName().getLocalPart()); + String currentMType = startElement.getAttributeByName( + M_TYPE).getValue(); + if (field.getGenericType() instanceof ParameterizedType) { + // determine what type of collection it is + ParameterizedType listType = (ParameterizedType) field + .getGenericType(); + collectionType = TypeUtils + .getCollectionType(currentMType); + Class listClass = (Class) listType + .getActualTypeArguments()[0]; + if (collectionType.toLowerCase().startsWith("edm")) { // simple type + // just add value to list + Object value = TypeUtils.convert(listClass, + reader.getElementText()); + ((List) entity).add(value); + } else { // complex type + obj = listClass.newInstance(); + // create a new instance and populate the properties + AtomFeedHandler.parseProperties(reader, + event.asStartElement(), obj); + ((List) entity).add(obj); + } + } + } + } + } + } catch (XMLStreamException e) { + Context.getCurrentLogger().warning( + "Cannot parse the collection due to: " + e.getMessage()); + } catch (SecurityException e) { + Context.getCurrentLogger().warning( + "Cannot parse the collection due to: " + e.getMessage()); + } catch (NoSuchFieldException e) { + Context.getCurrentLogger().warning( + "Cannot parse the collection due to: " + e.getMessage()); + } catch (InstantiationException e) { + Context.getCurrentLogger().warning( + "Cannot parse the collection due to: " + e.getMessage()); + } catch (IllegalAccessException e) { + Context.getCurrentLogger().warning( + "Cannot parse the collection due to: " + e.getMessage()); + } + return entity; + } + +} diff --git a/modules/org.restlet.ext.xml/module.xml b/modules/org.restlet.ext.xml/module.xml index 42fe1a3041..b5ecbc2a79 100644 --- a/modules/org.restlet.ext.xml/module.xml +++ b/modules/org.restlet.ext.xml/module.xml @@ -9,6 +9,7 @@ + diff --git a/modules/org.restlet.ext.xml/src/org/restlet/ext/xml/format/FormatParser.java b/modules/org.restlet.ext.xml/src/org/restlet/ext/xml/format/FormatParser.java new file mode 100644 index 0000000000..586b30cf4a --- /dev/null +++ b/modules/org.restlet.ext.xml/src/org/restlet/ext/xml/format/FormatParser.java @@ -0,0 +1,28 @@ +package org.restlet.ext.xml.format; + +import java.io.Reader; + +/** + * Deals with parsing the resulting stream into a Entry or + * Feed and converting it to a OEntity. The + * implementation depends on the format Atom or Json. + * + * @param Atom or json + * + * @author Onkar Dhuri + * + * @see Entry + * @see Feed + * @see OEntity + */ +public interface FormatParser { + + /** + * Parses the feed from reader + * + * @param reader the reader + * @return the t + */ + T parse(Reader reader); + +} diff --git a/modules/org.restlet.ext.xml/src/org/restlet/ext/xml/format/XmlFormatParser.java b/modules/org.restlet.ext.xml/src/org/restlet/ext/xml/format/XmlFormatParser.java new file mode 100644 index 0000000000..61ca232479 --- /dev/null +++ b/modules/org.restlet.ext.xml/src/org/restlet/ext/xml/format/XmlFormatParser.java @@ -0,0 +1,252 @@ +package org.restlet.ext.xml.format; + +import javax.xml.namespace.QName; +import javax.xml.stream.XMLStreamReader; +import javax.xml.stream.events.Attribute; +import javax.xml.stream.events.StartElement; +import javax.xml.stream.events.XMLEvent; + +import org.core4j.Enumerable; + +/** + * The Class XmlFormatParser. + * + * @author Onkar Dhuri + */ +public class XmlFormatParser { + + public static final String NS_APP = "http://www.w3.org/2007/app"; + public static final String NS_XML = "http://www.w3.org/XML/1998/namespace"; + public static final String NS_ATOM = "http://www.w3.org/2005/Atom"; + + public static final String NS_METADATA = "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"; + public static final String NS_DATASERVICES = "http://schemas.microsoft.com/ado/2007/08/dataservices"; + public static final String NS_SCHEME = "http://schemas.microsoft.com/ado/2007/08/dataservices/scheme"; + public static final String NS_RELATED = "http://schemas.microsoft.com/ado/2007/08/dataservices/related/"; + public static final String NS_EDM2006 = "http://schemas.microsoft.com/ado/2006/04/edm"; // edm 1.0 + public static final String NS_EDM2007 = "http://schemas.microsoft.com/ado/2007/05/edm"; // edm 1.1 + public static final String NS_EDM2008_1 = "http://schemas.microsoft.com/ado/2008/01/edm"; // edm 1.2 + public static final String NS_EDM2008_9 = "http://schemas.microsoft.com/ado/2008/09/edm"; // edm 2.0 + public static final String NS_EDM2009_8 = "http://schemas.microsoft.com/ado/2009/08/edm"; // edm ??? + public static final String NS_EDM2009_11 = "http://schemas.microsoft.com/ado/2009/11/edm"; // edm 3.0 + public static final String NS_EDMX = "http://schemas.microsoft.com/ado/2007/06/edmx"; + public static final String NS_EDMANNOTATION = "http://schemas.microsoft.com/ado/2009/02/edm/annotation"; + public static final String NS_CUSTOM_EDMANNOTATION = "http://www.lgc.com/dsdataserver/annotation"; + + public static final QName EDMX_EDMX = new QName(NS_EDMX, "Edmx"); + public static final QName EDMX_DATASERVICES = new QName(NS_EDMX, "DataServices"); + + public static final QName EDM2006_SCHEMA = new QName(NS_EDM2006, "Schema"); + public static final QName EDM2006_ENTITYTYPE = new QName(NS_EDM2006, "EntityType"); + public static final QName EDM2006_ASSOCIATION = new QName(NS_EDM2006, "Association"); + public static final QName EDM2006_COMPLEXTYPE = new QName(NS_EDM2006, "ComplexType"); + public static final QName EDM2006_ENTITYCONTAINER = new QName(NS_EDM2006, "EntityContainer"); + public static final QName EDM2006_ENTITYSET = new QName(NS_EDM2006, "EntitySet"); + public static final QName EDM2006_ASSOCIATIONSET = new QName(NS_EDM2006, "AssociationSet"); + public static final QName EDM2006_FUNCTIONIMPORT = new QName(NS_EDM2006, "FunctionImport"); + public static final QName EDM2006_PARAMETER = new QName(NS_EDM2006, "Parameter"); + public static final QName EDM2006_END = new QName(NS_EDM2006, "End"); + public static final QName EDM2006_ONDELETE = new QName(NS_EDM2006, "OnDelete"); + public static final QName EDM2006_REFCONSTRAINT = new QName(NS_EDM2006, "ReferentialConstraint"); + public static final QName EDM2006_PRINCIPAL = new QName(NS_EDM2006, "Principal"); + public static final QName EDM2006_DEPENDENT = new QName(NS_EDM2006, "Dependent"); + public static final QName EDM2006_PROPERTYREF = new QName(NS_EDM2006, "PropertyRef"); + public static final QName EDM2006_PROPERTY = new QName(NS_EDM2006, "Property"); + public static final QName EDM2006_NAVIGATIONPROPERTY = new QName(NS_EDM2006, "NavigationProperty"); + + public static final QName EDM2007_SCHEMA = new QName(NS_EDM2007, "Schema"); + public static final QName EDM2007_ENTITYTYPE = new QName(NS_EDM2007, "EntityType"); + public static final QName EDM2007_ASSOCIATION = new QName(NS_EDM2007, "Association"); + public static final QName EDM2007_COMPLEXTYPE = new QName(NS_EDM2007, "ComplexType"); + public static final QName EDM2007_ENTITYCONTAINER = new QName(NS_EDM2007, "EntityContainer"); + public static final QName EDM2007_ENTITYSET = new QName(NS_EDM2007, "EntitySet"); + public static final QName EDM2007_ASSOCIATIONSET = new QName(NS_EDM2007, "AssociationSet"); + public static final QName EDM2007_FUNCTIONIMPORT = new QName(NS_EDM2007, "FunctionImport"); + public static final QName EDM2007_PARAMETER = new QName(NS_EDM2007, "Parameter"); + public static final QName EDM2007_END = new QName(NS_EDM2007, "End"); + public static final QName EDM2007_ONDELETE = new QName(NS_EDM2007, "OnDelete"); + public static final QName EDM2007_REFCONSTRAINT = new QName(NS_EDM2007, "ReferentialConstraint"); + public static final QName EDM2007_PRINCIPAL = new QName(NS_EDM2007, "Principal"); + public static final QName EDM2007_DEPENDENT = new QName(NS_EDM2007, "Dependent"); + public static final QName EDM2007_PROPERTYREF = new QName(NS_EDM2007, "PropertyRef"); + public static final QName EDM2007_PROPERTY = new QName(NS_EDM2007, "Property"); + public static final QName EDM2007_NAVIGATIONPROPERTY = new QName(NS_EDM2007, "NavigationProperty"); + + public static final QName EDM2008_1_SCHEMA = new QName(NS_EDM2008_1, "Schema"); + public static final QName EDM2008_1_ENTITYTYPE = new QName(NS_EDM2008_1, "EntityType"); + public static final QName EDM2008_1_ASSOCIATION = new QName(NS_EDM2008_1, "Association"); + public static final QName EDM2008_1_COMPLEXTYPE = new QName(NS_EDM2008_1, "ComplexType"); + public static final QName EDM2008_1_ENTITYCONTAINER = new QName(NS_EDM2008_1, "EntityContainer"); + public static final QName EDM2008_1_ENTITYSET = new QName(NS_EDM2008_1, "EntitySet"); + public static final QName EDM2008_1_ASSOCIATIONSET = new QName(NS_EDM2008_1, "AssociationSet"); + public static final QName EDM2008_1_FUNCTIONIMPORT = new QName(NS_EDM2008_1, "FunctionImport"); + public static final QName EDM2008_1_PARAMETER = new QName(NS_EDM2008_1, "Parameter"); + public static final QName EDM2008_1_END = new QName(NS_EDM2008_1, "End"); + public static final QName EDM2008_1_ONDELETE = new QName(NS_EDM2008_1, "OnDelete"); + public static final QName EDM2008_1_REFCONSTRAINT = new QName(NS_EDM2008_1, "ReferentialConstraint"); + public static final QName EDM2008_1_PRINCIPAL = new QName(NS_EDM2008_1, "Principal"); + public static final QName EDM2008_1_DEPENDENT = new QName(NS_EDM2008_1, "Dependent"); + public static final QName EDM2008_1_PROPERTYREF = new QName(NS_EDM2008_1, "PropertyRef"); + public static final QName EDM2008_1_PROPERTY = new QName(NS_EDM2008_1, "Property"); + public static final QName EDM2008_1_NAVIGATIONPROPERTY = new QName(NS_EDM2008_1, "NavigationProperty"); + + public static final QName EDM2008_9_SCHEMA = new QName(NS_EDM2008_9, "Schema"); + public static final QName EDM2008_9_ENTITYTYPE = new QName(NS_EDM2008_9, "EntityType"); + public static final QName EDM2008_9_ASSOCIATION = new QName(NS_EDM2008_9, "Association"); + public static final QName EDM2008_9_COMPLEXTYPE = new QName(NS_EDM2008_9, "ComplexType"); + public static final QName EDM2008_9_ENTITYCONTAINER = new QName(NS_EDM2008_9, "EntityContainer"); + public static final QName EDM2008_9_ENTITYSET = new QName(NS_EDM2008_9, "EntitySet"); + public static final QName EDM2008_9_ASSOCIATIONSET = new QName(NS_EDM2008_9, "AssociationSet"); + public static final QName EDM2008_9_FUNCTIONIMPORT = new QName(NS_EDM2008_9, "FunctionImport"); + public static final QName EDM2008_9_PARAMETER = new QName(NS_EDM2008_9, "Parameter"); + public static final QName EDM2008_9_END = new QName(NS_EDM2008_9, "End"); + public static final QName EDM2008_9_ONDELETE = new QName(NS_EDM2008_9, "OnDelete"); + public static final QName EDM2008_9_REFCONSTRAINT = new QName(NS_EDM2008_9, "ReferentialConstraint"); + public static final QName EDM2008_9_PRINCIPAL = new QName(NS_EDM2008_9, "Principal"); + public static final QName EDM2008_9_DEPENDENT = new QName(NS_EDM2008_9, "Dependent"); + public static final QName EDM2008_9_PROPERTYREF = new QName(NS_EDM2008_9, "PropertyRef"); + public static final QName EDM2008_9_PROPERTY = new QName(NS_EDM2008_9, "Property"); + public static final QName EDM2008_9_NAVIGATIONPROPERTY = new QName(NS_EDM2008_9, "NavigationProperty"); + + public static final QName EDM2009_8_SCHEMA = new QName(NS_EDM2009_8, "Schema"); + public static final QName EDM2009_8_ENTITYTYPE = new QName(NS_EDM2009_8, "EntityType"); + public static final QName EDM2009_8_ASSOCIATION = new QName(NS_EDM2009_8, "Association"); + public static final QName EDM2009_8_COMPLEXTYPE = new QName(NS_EDM2009_8, "ComplexType"); + public static final QName EDM2009_8_ENTITYCONTAINER = new QName(NS_EDM2009_8, "EntityContainer"); + public static final QName EDM2009_8_ENTITYSET = new QName(NS_EDM2009_8, "EntitySet"); + public static final QName EDM2009_8_ASSOCIATIONSET = new QName(NS_EDM2009_8, "AssociationSet"); + public static final QName EDM2009_8_FUNCTIONIMPORT = new QName(NS_EDM2009_8, "FunctionImport"); + public static final QName EDM2009_8_PARAMETER = new QName(NS_EDM2009_8, "Parameter"); + public static final QName EDM2009_8_END = new QName(NS_EDM2009_8, "End"); + public static final QName EDM2009_8_ONDELETE = new QName(NS_EDM2009_8, "OnDelete"); + public static final QName EDM2009_8_REFCONSTRAINT = new QName(NS_EDM2009_8, "ReferentialConstraint"); + public static final QName EDM2009_8_PRINCIPAL = new QName(NS_EDM2009_8, "Principal"); + public static final QName EDM2009_8_DEPENDENT = new QName(NS_EDM2009_8, "Dependent"); + public static final QName EDM2009_8_PROPERTYREF = new QName(NS_EDM2009_8, "PropertyRef"); + public static final QName EDM2009_8_PROPERTY = new QName(NS_EDM2009_8, "Property"); + public static final QName EDM2009_8_NAVIGATIONPROPERTY = new QName(NS_EDM2009_8, "NavigationProperty"); + + public static final QName EDM2009_11_SCHEMA = new QName(NS_EDM2009_11, "Schema"); + public static final QName EDM2009_11_ENTITYTYPE = new QName(NS_EDM2009_11, "EntityType"); + public static final QName EDM2009_11_ASSOCIATION = new QName(NS_EDM2009_11, "Association"); + public static final QName EDM2009_11_COMPLEXTYPE = new QName(NS_EDM2009_11, "ComplexType"); + public static final QName EDM2009_11_ENTITYCONTAINER = new QName(NS_EDM2009_11, "EntityContainer"); + public static final QName EDM2009_11_ENTITYSET = new QName(NS_EDM2009_11, "EntitySet"); + public static final QName EDM2009_11_ASSOCIATIONSET = new QName(NS_EDM2009_11, "AssociationSet"); + public static final QName EDM2009_11_FUNCTIONIMPORT = new QName(NS_EDM2009_11, "FunctionImport"); + public static final QName EDM2009_11_PARAMETER = new QName(NS_EDM2009_11, "Parameter"); + public static final QName EDM2009_11_END = new QName(NS_EDM2009_11, "End"); + public static final QName EDM2009_11_ONDELETE = new QName(NS_EDM2009_11, "OnDelete"); + public static final QName EDM2009_11_REFCONSTRAINT = new QName(NS_EDM2009_11, "ReferentialConstraint"); + public static final QName EDM2009_11_PRINCIPAL = new QName(NS_EDM2009_11, "Principal"); + public static final QName EDM2009_11_DEPENDENT = new QName(NS_EDM2009_11, "Dependent"); + public static final QName EDM2009_11_PROPERTYREF = new QName(NS_EDM2009_11, "PropertyRef"); + public static final QName EDM2009_11_PROPERTY = new QName(NS_EDM2009_11, "Property"); + public static final QName EDM2009_11_NAVIGATIONPROPERTY = new QName(NS_EDM2009_11, "NavigationProperty"); + + public static final QName ATOM_FEED = new QName(NS_ATOM, "feed"); + public static final QName ATOM_ENTRY = new QName(NS_ATOM, "entry"); + public static final QName ATOM_ID = new QName(NS_ATOM, "id"); + public static final QName ATOM_TITLE = new QName(NS_ATOM, "title"); + public static final QName ATOM_SUMMARY = new QName(NS_ATOM, "summary"); + public static final QName ATOM_UPDATED = new QName(NS_ATOM, "updated"); + public static final QName ATOM_CATEGORY = new QName(NS_ATOM, "category"); + public static final QName ATOM_CONTENT = new QName(NS_ATOM, "content"); + public static final QName ATOM_LINK = new QName(NS_ATOM, "link"); + public static final QName ATOM_AUTHOR = new QName(NS_ATOM, "author"); + public static final QName ATOM_NAME = new QName(NS_ATOM, "name"); + public static final QName ATOM_EMAIL = new QName(NS_ATOM, "email"); + + public static final QName APP_WORKSPACE = new QName(NS_APP, "workspace"); + public static final QName APP_SERVICE = new QName(NS_APP, "service"); + public static final QName APP_COLLECTION = new QName(NS_APP, "collection"); + public static final QName APP_ACCEPT = new QName(NS_APP, "accept"); + + public static final QName M_ETAG = new QName(NS_METADATA, "etag"); + public static final QName M_PROPERTIES = new QName(NS_METADATA, "properties"); + public static final QName M_ACTION = new QName(NS_METADATA, "action"); + public static final QName M_FUNCTION = new QName(NS_METADATA, "function"); + public static final QName M_TYPE = new QName(NS_METADATA, "type"); + public static final QName M_NULL = new QName(NS_METADATA, "null"); + public static final QName M_INLINE = new QName(NS_METADATA, "inline"); + public static final QName M_MIMETYPE = new QName(NS_METADATA, "MimeType"); + public static final QName M_FC_TARGETPATH = new QName(NS_METADATA, "FC_TargetPath"); + public static final QName M_FC_CONTENTKIND = new QName(NS_METADATA, "FC_ContentKind"); + public static final QName M_FC_KEEPINCONTENT = new QName(NS_METADATA, "FC_KeepInContent"); + public static final QName M_FC_EPMCONTENTKIND = new QName(NS_METADATA, "FC_EpmContentKind"); + public static final QName M_FC_EPMKEEPINCONTENT = new QName(NS_METADATA, "FC_EpmKeepInContent"); + public static final QName M_FC_NSPREFIX = new QName(NS_METADATA, "FC_NsPrefix"); + public static final QName M_FC_NSURI = new QName(NS_METADATA, "FC_NsUri"); + + public static final QName DATASERVICES_ELEMENT = new QName(NS_DATASERVICES, "element"); // a collection element + + public static final QName XML_BASE = new QName(NS_XML, "base"); + + protected static boolean isStartElement(XMLEvent event, QName... names) { + if (!event.isStartElement()) { + return false; + } + QName name = new QName(event.asStartElement().getName().getNamespaceURI(), event.asStartElement().getName().getLocalPart()); + return Enumerable.create(names).contains(name); + + } + + protected static boolean isElement(XMLEvent event, QName... names) { + QName name = new QName(event.asStartElement().getName().getNamespaceURI(), event.asStartElement().getName().getLocalPart()); + return Enumerable.create(names).contains(name); + + } + + protected static boolean isEndElement(XMLEvent event, QName qname) { + if (!event.isEndElement()) { + return false; + } + QName name = event.asEndElement().getName(); + return name.getNamespaceURI().equals(qname.getNamespaceURI()) + && name.getLocalPart().equals(qname.getLocalPart()); + } + + protected static String urlCombine(String base, String rel) { + if (!base.endsWith("/") && !rel.startsWith("/")) + base = base + "/"; + return base + rel; + } + + protected static String getAttributeValueIfExists(StartElement element, String localName) { + return getAttributeValueIfExists(element, new QName(null, localName)); + } + + protected static String getAttributeValueIfExists(StartElement element, QName attName) { + Attribute rt = element.getAttributeByName(attName); + return rt == null ? null : rt.getValue(); + } + + protected static boolean isStartElement(XMLStreamReader reader, QName... names) { + if (!reader.isStartElement()) { + return false; + } + QName name = new QName(reader.getNamespaceURI(), reader.getLocalName()); + return Enumerable.create(names).contains(name); + + } + + protected static boolean isElement(XMLStreamReader reader, QName... names) { + QName name = new QName(reader.getNamespaceURI(), reader.getLocalName()); + return Enumerable.create(names).contains(name); + + } + + protected static boolean isEndElement(XMLStreamReader reader, QName qname) { + if (!reader.isEndElement()) { + return false; + } + QName name = new QName(reader.getNamespaceURI(), reader.getLocalName()); + return name.getNamespaceURI().equals(qname.getNamespaceURI()) + && name.getLocalPart().equals(qname.getLocalPart()); + } + + protected static String getAttributeValueIfExists(XMLStreamReader reader, String attName) { + return reader.getAttributeValue(null, attName); + } + +} diff --git a/modules/org.restlet.test/module.xml b/modules/org.restlet.test/module.xml index c5c4001d89..22078f2f5d 100644 --- a/modules/org.restlet.test/module.xml +++ b/modules/org.restlet.test/module.xml @@ -12,6 +12,7 @@ + diff --git a/modules/org.restlet.test/src/org/restlet/test/batch/crud/Cafe.java b/modules/org.restlet.test/src/org/restlet/test/batch/crud/Cafe.java new file mode 100644 index 0000000000..4b746a8097 --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/batch/crud/Cafe.java @@ -0,0 +1,119 @@ +package org.restlet.test.batch.crud; + +/** + * Generated by the generator tool for the RestletBatch service extension for + * the Restlet framework.
+ * + * @see Metadata of the + * target Restlet Data Services + * + */ +public class Cafe { + + /** The city. */ + private String city; + + /** The id. */ + private String id; + + /** The name. */ + private String name; + + /** The zip code. */ + private int zipCode; + + /** + * Constructor without parameter. + * + */ + public Cafe() { + super(); + } + + /** + * Constructor. + * + * @param id + * The identifiant value of the entity. + */ + public Cafe(String id) { + this(); + this.id = id; + } + + /** + * Returns the value of the city attribute. + * + * @return The value of the city attribute. + */ + public String getCity() { + return city; + } + + /** + * Returns the value of the id attribute. + * + * @return The value of the id attribute. + */ + public String getId() { + return id; + } + + /** + * Returns the value of the name attribute. + * + * @return The value of the name attribute. + */ + public String getName() { + return name; + } + + /** + * Returns the value of the zipCode attribute. + * + * @return The value of the zipCode attribute. + */ + public int getZipCode() { + return zipCode; + } + + /** + * Sets the value of the city attribute. + * + * @param city + * the new city + */ + public void setCity(String city) { + this.city = city; + } + + /** + * Sets the value of the id attribute. + * + * @param id + * the new id + */ + public void setId(String id) { + this.id = id; + } + + /** + * Sets the value of the name attribute. + * + * @param name + * the new name + */ + public void setName(String name) { + this.name = name; + } + + /** + * Sets the value of the zipCode attribute. + * + * @param zipCode + * the new zip code + */ + public void setZipCode(int zipCode) { + this.zipCode = zipCode; + } +} diff --git a/modules/org.restlet.test/src/org/restlet/test/batch/crud/CafeCrudApplication.java b/modules/org.restlet.test/src/org/restlet/test/batch/crud/CafeCrudApplication.java new file mode 100644 index 0000000000..c882210dac --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/batch/crud/CafeCrudApplication.java @@ -0,0 +1,346 @@ +package org.restlet.test.batch.crud; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.List; +import java.util.logging.Logger; + +import javax.ws.rs.core.MultivaluedMap; + +import org.jvnet.mimepull.Header; +import org.jvnet.mimepull.MIMEMessage; +import org.jvnet.mimepull.MIMEPart; +import org.restlet.Application; +import org.restlet.Context; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.Restlet; +import org.restlet.data.CharacterSet; +import org.restlet.data.LocalReference; +import org.restlet.data.MediaType; +import org.restlet.data.Method; +import org.restlet.data.Parameter; +import org.restlet.data.Protocol; +import org.restlet.data.Status; +import org.restlet.ext.odata.batch.util.BatchConstants; +import org.restlet.ext.odata.batch.util.BodyPart; +import org.restlet.ext.odata.batch.util.Multipart; +import org.restlet.representation.StringRepresentation; +import org.restlet.routing.Router; +import org.restlet.util.Series; + +/** + * Sample application that simulates the CUD operation on entities. + */ + +public class CafeCrudApplication extends Application { + + /** + * The Class dataLogger. + */ + private static class BatchTestRestlet extends Restlet { + + /** The Constant LOGGER. */ + private static final Logger LOGGER1 = Logger + .getLogger(CreateCafeTestCase.class.getName()); + + /** The batch id added to response. */ + private static boolean batchIdAddedToResponse = false; + + /** The builder. */ + private static StringBuilder builder = new StringBuilder(); + + /** The batch id. */ + private static String batchId = null; + + /** The batch xmlFilePath. */ + private static final String xmlFilePath = "/org/restlet/test/batch/xml/"; + + /** The file. */ + String fileName; + + /** + * Instantiates a new data logger. + * + * @param context + * the context + * @param file + * the file + * @param updatable + * the updatable + */ + public BatchTestRestlet(Context context, String xmlFileName, + boolean updatable) { + super(context); + this.fileName = xmlFileName; + } + + /* + * (non-Javadoc) + * + * @see org.restlet.Restlet#handle(org.restlet.Request, + * org.restlet.Response) + */ + @Override + public void handle(Request request, Response response) { + + if (Method.GET.equals(request.getMethod())) { + String filePath = xmlFilePath; + Response r = getContext().getClientDispatcher().handle( + new Request(Method.GET, LocalReference + .createClapReference(LocalReference.CLAP_CLASS, + filePath + fileName + ".xml"))); + response.setEntity(r.getEntity()); + response.setStatus(r.getStatus()); + + } else if (Method.POST.equals(request.getMethod())) { + String rep = null; + try { + rep = request.getEntity().getText(); + String[] split = rep.split("\r\n"); + for (int i = 0; i < split.length; i++) { + createResponse(split[i], builder); + } + response.setEntity(new StringRepresentation(builder + .toString())); + setResponse(response, Status.SUCCESS_OK); + } catch (IOException e) { + response.setStatus(Status.CLIENT_ERROR_BAD_REQUEST); + } + if (null != rep && !rep.isEmpty()) { + response.setStatus(Status.SUCCESS_OK); + } + + } + } + + /** + * Creates the response as per the method type. + * + * @param rep + * the rep + * @param sb + * the sb + * @throws IOException + * Signals that an I/O exception has occurred. + */ + private void createResponse(String rep, StringBuilder sb) + throws IOException { + String filePath = null; + if (rep.contains(Method.GET.toString())) { + LOGGER1.info("This is Get method"); + filePath = getFilePath("getCafeResponse.xml"); + readResponseAndFillBuffer(sb, filePath); + + } else if (rep.contains(Method.DELETE.toString())) { + LOGGER1.info("This is delete method"); + filePath = getFilePath("deleteCafeResponse.xml"); + readResponseAndFillBuffer(sb, filePath); + + } else if (rep.contains(Method.PUT.toString())) { + LOGGER1.info("This is put method"); + filePath = getFilePath("updateCafeResponse.xml"); + readResponseAndFillBuffer(sb, filePath); + + } else if (rep.contains(Method.POST.toString())) { + LOGGER1.info("This is post method"); + filePath = getFilePath("createCafeResponse.xml"); + readResponseAndFillBuffer(sb, filePath); + } else if (rep.contains("--changeset")) { + + sb.append("\n\n").append( + rep.replace("changeset", "changesetresponse")); + + } else if (rep.contains("--batch")) { + if (!batchIdAddedToResponse) { + filePath = getFilePath("parentBatchResponse.xml"); + String replace = rep.replace("batch", "batchresponse"); + if (replace.contains("batch")) { + String[] split = replace.split("_"); + batchId = split[1]; + } + readResponseAndFillBuffer(sb, filePath); + String str = sb.toString(); + str = str + .replace( + "batchresponse_1f82a90b-43f1-4276-b20a-59da3437dfa8", + "batchresponse_" + batchId); + String timeStamp = new SimpleDateFormat( + "yyyy:MM:dd_HH:mm:ss").format(Calendar + .getInstance().getTime()); + str = str.replace("Date: Mon, 23 June 2014 09:44:08 GMT", + "Date:" + timeStamp); + builder = new StringBuilder(str); + batchIdAddedToResponse = true; + } + builder.append("\n\n").append( + rep.replace("batch", "batchresponse")); + + } else if (rep + .contains("Content-Type: application/atom+xml;charset=utf-8")) { + + sb.append("\n\n") + .append(rep + .replace( + "Content-Type: application/atom+xml;charset=utf-8", + "")); + + } else if (rep.contains("Content-Type")) { + if (rep.contains("changeset")) { + sb.append("\n").append( + rep.replace("changeset", "changesetresponse")); + } else { + sb.append("\n").append( + rep.replace("Content-Type", "Content-Type")); + } + } else if (rep.contains("changeset_")) { + sb.append("\n").append( + rep.replace("changeset_", "changesetresponse_")); + } else if (rep.contains("Content-Transfer-Encoding:")) { + sb.append("\n").append( + rep.replace("Content-Transfer-Encoding", + "Content-Transfer-Encoding")); + } + + } + + /** + * Gets the complete xml filePath + * + * @param fileName + * the file name + * @return the file path + * @throws IOException + * Signals that an I/O exception has occurred. + */ + private String getFilePath(String fileName) throws IOException { + return new File(".").getCanonicalPath() + "/src" + xmlFilePath + + fileName; + } + + /** + * Read response and fill buffer. + * + * @param sb + * the sb + * @param filePath + * the file path + * @throws FileNotFoundException + * the file not found exception + * @throws IOException + * Signals that an I/O exception has occurred. + */ + private void readResponseAndFillBuffer(StringBuilder sb, String filePath) + throws FileNotFoundException, IOException { + InputStream is = new FileInputStream(new File(filePath)); + InputStreamReader isr = new InputStreamReader(is); + BufferedReader br = new BufferedReader(isr); + while (br.ready()) { + sb.append(br.readLine()).append("\n"); + + } + br.close(); + } + + /** + * Sets the response. + * + * @param response + * the response + * @param status + * the status + */ + private void setResponse(Response response, Status status) { + Series parameters = new Series( + Parameter.class); + parameters.add("boundary", "batchresponse_" + batchId); + MediaType mediaType = new MediaType( + MediaType.MULTIPART_MIXED.toString(), parameters); + response.getEntity().setMediaType(mediaType); + } + } + + /* + * (non-Javadoc) + * + * @see org.restlet.Application#createInboundRoot() + */ + @Override + public Restlet createInboundRoot() { + getMetadataService().setDefaultCharacterSet(CharacterSet.ISO_8859_1); + getConnectorService().getClientProtocols().add(Protocol.CLAP); + Router router = new Router(getContext()); + + router.attach("/$metadata", new BatchTestRestlet(getContext(), + "metadata", true)); + router.attach("/Cafes", new BatchTestRestlet(getContext(), "cafes", + true)); + router.attach("/Cafes('40')", new BatchTestRestlet(getContext(), + "cafesUpdatedRequest", true)); + router.attach("/$batch", new BatchTestRestlet(getContext(), "cafes", + true)); + + return router; + } + + /** + * Creates the multipart. + * + * A Multipart is a logical representation of a batch request or Chnagset.
+ * It is a set of multiple http requests/response. + * + * @param is + * the is + * @param mediaType + * the media type + * @return the multipart + * @throws IOException + * Signals that an I/O exception has occurred. + */ + public static Multipart createMultipart(InputStream is, MediaType mediaType) + throws IOException { + // create a multipart + Multipart multipart = new Multipart(); + // set its mediatype + multipart.setMediaType(mediaType); + + MIMEMessage mimeMessage = new MIMEMessage(is, mediaType.getParameters() + .getFirstValue(BatchConstants.BATCH_BOUNDARY)); + List attachments = mimeMessage.getAttachments(); + for (MIMEPart mimePart : attachments) { + BodyPart bodyPart = new BodyPart(mimePart); + // copy headers into bodyparts + copyHeaders(bodyPart, mimePart); + bodyPart.setMediaType(new MediaType(bodyPart.getHeaders().getFirst( + BatchConstants.HTTP_HEADER_CONTENT_TYPE))); + multipart.addBodyParts(bodyPart); + + } + return multipart; + } + + /** + * Copy headers. + * + * @param bodyPart + * the body part + * @param mimePart + * the mime part + */ + private static void copyHeaders(BodyPart bodyPart, MIMEPart mimePart) { + MultivaluedMap bpHeaders = bodyPart.getHeaders(); + List mHeaders = mimePart.getAllHeaders(); + for (Header header : mHeaders) { + bpHeaders.add(header.getName(), header.getValue()); + } + + } + +} diff --git a/modules/org.restlet.test/src/org/restlet/test/batch/crud/CafeService.java b/modules/org.restlet.test/src/org/restlet/test/batch/crud/CafeService.java new file mode 100644 index 0000000000..9133e0af10 --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/batch/crud/CafeService.java @@ -0,0 +1,98 @@ +package org.restlet.test.batch.crud; + +import org.restlet.ext.odata.Query; +import org.restlet.ext.odata.Service; +import org.restlet.ext.odata.batch.request.BatchRequest; +import org.restlet.ext.odata.batch.request.impl.BatchRequestImpl; +import org.restlet.ext.odata.batch.request.impl.CreateEntityRequest; +import org.restlet.test.ext.odata.crud.Cafe; + +/** + * Generated by the generator tool for the RestletBatch service extension for + * the Restlet framework.
+ * + * @see Metadata of the + * target RestletBatch service + * + * + */ +public class CafeService extends Service { + + /** + * Constructor. + * + */ + public CafeService() { + super("http://localhost:8111/Cafe.svc/"); + } + + /** + * Adds a new entity to the service. + * + * @param entity + * The entity to add to the service. + * @return the creates the entity request + * @throws Exception + * the exception + */ + public CreateEntityRequest addEntity(Cafe entity) throws Exception { + return addEntity("/Cafes", entity); + } + + /** + * Creates a query for cafe entities hosted by this service. + * + * @param subpath + * The path to this entity relatively to the service URI. + * @return A query object. + */ + public Query createCafeQuery(String subpath) { + return createQuery(subpath, Cafe.class); + } + + /** + * Updates a query for cafe entities hosted by this service. + * + * @param subpath + * The path to this entity relatively to the service URI. + * @return A query object. + */ + + public Query updateCafeQuery(String subpath) { + return createQuery(subpath, Cafe.class); + } + + /** + * Deletes a query for cafe entities hosted by this service. + * + * @param subpath + * The path to this entity relatively to the service URI. + * @return A query object. + */ + + public Query deleteCafeQuery(String subpath) { + return createQuery(subpath, Cafe.class); + } + + /** + * Gets a query for cafe entities hosted by this service. + * + * @param subpath + * The path to this entity relatively to the service URI. + * @return A query object. + */ + + public Query getCafeQuery(String subpath) { + return createQuery(subpath, Cafe.class); + } + + /** + * Method to perform batch operations. It maintains the list of + * clientBatchRequests within a batch. + * + * @return list of request in batch. + */ + public BatchRequest createBatchRequest() { + return new BatchRequestImpl(this); + } +} diff --git a/modules/org.restlet.test/src/org/restlet/test/batch/crud/CreateCafeTestCase.java b/modules/org.restlet.test/src/org/restlet/test/batch/crud/CreateCafeTestCase.java new file mode 100644 index 0000000000..a5f342f0ed --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/batch/crud/CreateCafeTestCase.java @@ -0,0 +1,154 @@ +package org.restlet.test.batch.crud; + +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.ws.rs.core.MultivaluedMap; + +import junit.framework.Assert; + +import org.restlet.Component; +import org.restlet.Response; +import org.restlet.data.Protocol; +import org.restlet.data.Status; +import org.restlet.ext.odata.Query; +import org.restlet.ext.odata.batch.request.BatchRequest; +import org.restlet.ext.odata.batch.request.impl.ChangeSetRequestImpl; +import org.restlet.ext.odata.batch.request.impl.CreateEntityRequest; +import org.restlet.ext.odata.batch.response.BatchResponse; +import org.restlet.ext.odata.batch.response.ChangeSetResponse; +import org.restlet.test.RestletTestCase; +import org.restlet.test.ext.odata.crud.Cafe; + +/** + * Test case for RestletBatch service for CREATE operation on entities. + * + */ +public class CreateCafeTestCase extends RestletTestCase { + + /** The Constant cafeName. */ + private static final String cafeName = "TestName"; + + /** The Constant cafeId. */ + private static final String cafeId = "40"; + + /** The Constant cafeZipCode. */ + private static final int cafeZipCode = 111111; + + /** The Constant LOGGER. */ + private static final Logger LOGGER = Logger + .getLogger(CreateCafeTestCase.class.getName()); + + /** The Constant cafeCity. */ + private static final String cafeCity = "TestCity"; + + /** Inner component. */ + private Component component = new Component(); + + /** OData service used for all tests. */ + @SuppressWarnings("unused") + private CafeService service; + + /* + * (non-Javadoc) + * + * @see org.restlet.test.RestletTestCase#setUp() + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + + component.getServers().add(Protocol.HTTP, 8111); + component.getClients().add(Protocol.CLAP); + component.getDefaultHost().attach("/Cafe.svc", + new CafeCrudApplication()); + component.start(); + + service = new CafeService(); + } + + /* + * (non-Javadoc) + * + * @see org.restlet.test.RestletTestCase#tearDown() + */ + @Override + protected void tearDown() throws Exception { + component.stop(); + component = null; + super.tearDown(); + } + + /** + * Test method for CREATE operation on simple entities. + */ + public void testCreate() { + CafeService service = new CafeService(); + + // create. + Cafe cafe = new Cafe(); + cafe.setId(cafeId); + cafe.setName(cafeName); + cafe.setCity(cafeCity); + cafe.setZipCode(cafeZipCode); + try { + + BatchRequest br = service.createBatchRequest(); + ChangeSetRequestImpl changeSetRequest = new ChangeSetRequestImpl(); + // Create request + CreateEntityRequest createEntityRequest = new CreateEntityRequest( + service, cafe); + changeSetRequest.addRequest(createEntityRequest); + List responses = br.addRequest(changeSetRequest) + .execute(); + dumpResponse(responses); + // Assert for response. + Query createquery = service.createCafeQuery("/Cafes"); + Cafe cafe1 = createquery.iterator().next(); + assertEquals(cafeName, cafe1.getName()); + assertEquals(cafeId, cafe1.getId()); + assertEquals(cafeZipCode, cafe1.getZipCode()); + Response latestResponse = createquery.getService() + .getLatestResponse(); + latestResponse = createquery.getService().getLatestResponse(); + assertEquals(Status.SUCCESS_OK, latestResponse.getStatus()); + } catch (Exception ex) { + LOGGER.log(Level.SEVERE, ex.getMessage()); + Assert.fail(); + } + } + + /** + * Dump response. + * + * @param responses + * the responses + */ + @SuppressWarnings("unchecked") + public static void dumpResponse(List responses) { + for (BatchResponse batchResponse : responses) { + Object entity = batchResponse.getEntity(); + if (batchResponse instanceof ChangeSetResponse) { + LOGGER.info("Dumping changeset"); + dumpResponse((List) entity); + LOGGER.info("Done with changeset"); + } else { + LOGGER.info("Status =" + batchResponse.getStatus()); + LOGGER.info("Entity = " + entity); + MultivaluedMap headers = batchResponse + .getHeaders(); + if (headers != null) { + Set keySet = headers.keySet(); + LOGGER.info("Headers : "); + for (String key : keySet) { + List value = headers.get(key); + LOGGER.info("Key =" + key + "/t" + "value = " + value); + } + } + } + } + } + +} \ No newline at end of file diff --git a/modules/org.restlet.test/src/org/restlet/test/batch/crud/CreateDeleteChangeSetTestCase.java b/modules/org.restlet.test/src/org/restlet/test/batch/crud/CreateDeleteChangeSetTestCase.java new file mode 100644 index 0000000000..e16e2e44a6 --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/batch/crud/CreateDeleteChangeSetTestCase.java @@ -0,0 +1,166 @@ +package org.restlet.test.batch.crud; + +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.ws.rs.core.MultivaluedMap; + +import junit.framework.Assert; + +import org.restlet.Component; +import org.restlet.Response; +import org.restlet.data.Protocol; +import org.restlet.data.Status; +import org.restlet.ext.odata.Query; +import org.restlet.ext.odata.batch.request.BatchRequest; +import org.restlet.ext.odata.batch.request.impl.ChangeSetRequestImpl; +import org.restlet.ext.odata.batch.request.impl.CreateEntityRequest; +import org.restlet.ext.odata.batch.request.impl.DeleteEntityRequest; +import org.restlet.ext.odata.batch.response.BatchResponse; +import org.restlet.ext.odata.batch.response.ChangeSetResponse; +import org.restlet.test.RestletTestCase; +import org.restlet.test.ext.odata.crud.Cafe; + +/** + * Test case for RestletBatch service for CREATE & DELETE operation on entities. + * + */ +public class CreateDeleteChangeSetTestCase extends RestletTestCase { + + /** The Constant cafeName. */ + private static final String cafeName = "TestName"; + + /** The Constant cafeId. */ + private static final String cafeId = "40"; + + /** The Constant cafeZipCode. */ + private static final int cafeZipCode = 111111; + + /** The Constant LOGGER. */ + private static final Logger LOGGER = Logger + .getLogger(CreateDeleteChangeSetTestCase.class.getName()); + + /** The Constant cafeCity. */ + private static final String cafeCity = "TestCity"; + + /** Inner component. */ + private Component component = new Component(); + + /** OData service used for all tests. */ + @SuppressWarnings("unused") + private CafeService service; + + /* + * (non-Javadoc) + * + * @see org.restlet.test.RestletTestCase#setUp() + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + + component.getServers().add(Protocol.HTTP, 8111); + component.getClients().add(Protocol.CLAP); + component.getDefaultHost().attach("/Cafe.svc", + new CafeCrudApplication()); + component.start(); + + service = new CafeService(); + } + + /* + * (non-Javadoc) + * + * @see org.restlet.test.RestletTestCase#tearDown() + */ + @Override + protected void tearDown() throws Exception { + component.stop(); + component = null; + super.tearDown(); + } + + /** + * Test method for CREATE & DELETE operation on simple entities. + */ + public void testCreateDelete() { + CafeService service = new CafeService(); + + // create & delete + Cafe cafe = new Cafe(); + cafe.setId(cafeId); + cafe.setName(cafeName); + cafe.setCity(cafeCity); + cafe.setZipCode(cafeZipCode); + + try { + + BatchRequest br = service.createBatchRequest(); + ChangeSetRequestImpl changeSetRequest = new ChangeSetRequestImpl(); + // Create request + CreateEntityRequest createEntityRequest = new CreateEntityRequest( + service, cafe); + DeleteEntityRequest deleteEntityRequest = new DeleteEntityRequest( + service, cafe); + changeSetRequest.addRequest(createEntityRequest); + changeSetRequest.addRequest(deleteEntityRequest); + List responses = br.addRequest(changeSetRequest) + .execute(); + for (BatchResponse batchResponse : responses) { + batchResponse.getEntity(); + dumpResponse(responses); + } + + // Assert for response. + Query createquery = service.createCafeQuery("/Cafes"); + Cafe cafe1 = createquery.iterator().next(); + assertEquals(cafeName, cafe1.getName()); + assertEquals(cafeId, cafe1.getId()); + assertEquals(cafeZipCode, cafe1.getZipCode()); + Response latestResponse = createquery.getService() + .getLatestResponse(); + latestResponse = createquery.getService().getLatestResponse(); + assertTrue(latestResponse.getStatus().isSuccess()); + Query deletequery = service.deleteCafeQuery(("/Cafes('40')")); + latestResponse = deletequery.getService().getLatestResponse(); + assertEquals(Status.SUCCESS_OK, latestResponse.getStatus()); + } catch (Exception ex) { + LOGGER.log(Level.SEVERE, ex.getMessage()); + Assert.fail(); + } + } + + /** + * Dump response. + * + * @param responses + * the responses + */ + @SuppressWarnings("unchecked") + public static void dumpResponse(List responses) { + for (BatchResponse batchResponse : responses) { + Object entity = batchResponse.getEntity(); + if (batchResponse instanceof ChangeSetResponse) { + LOGGER.info("Dumping changeset"); + dumpResponse((List) entity); + LOGGER.info("Done with changeset"); + } else { + LOGGER.info("Status =" + batchResponse.getStatus()); + LOGGER.info("Entity = " + entity); + MultivaluedMap headers = batchResponse + .getHeaders(); + if (headers != null) { + Set keySet = headers.keySet(); + LOGGER.info("Headers : "); + for (String key : keySet) { + List value = headers.get(key); + LOGGER.info("Key =" + key + "/t" + "value = " + value); + } + } + } + } + } + +} \ No newline at end of file diff --git a/modules/org.restlet.test/src/org/restlet/test/batch/crud/CreateUpdateChangeSetTestCase.java b/modules/org.restlet.test/src/org/restlet/test/batch/crud/CreateUpdateChangeSetTestCase.java new file mode 100644 index 0000000000..5b47eaa34c --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/batch/crud/CreateUpdateChangeSetTestCase.java @@ -0,0 +1,172 @@ +package org.restlet.test.batch.crud; + +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.ws.rs.core.MultivaluedMap; + +import junit.framework.Assert; + +import org.restlet.Component; +import org.restlet.Response; +import org.restlet.data.Protocol; +import org.restlet.data.Status; +import org.restlet.ext.odata.Query; +import org.restlet.ext.odata.batch.request.BatchRequest; +import org.restlet.ext.odata.batch.request.impl.ChangeSetRequestImpl; +import org.restlet.ext.odata.batch.request.impl.CreateEntityRequest; +import org.restlet.ext.odata.batch.request.impl.UpdateEntityRequest; +import org.restlet.ext.odata.batch.response.BatchResponse; +import org.restlet.ext.odata.batch.response.ChangeSetResponse; +import org.restlet.test.RestletTestCase; +import org.restlet.test.ext.odata.crud.Cafe; + +/** + * Test case for RestletBatch service for CREATE & UPDATE operation on entities. + * + */ +public class CreateUpdateChangeSetTestCase extends RestletTestCase { + + /** The Constant cafeName. */ + private static final String cafeName = "TestName"; + + /** The Constant cafeId. */ + private static final String cafeId = "40"; + + /** The Constant cafeZipCode. */ + private static final int cafeZipCode = 111111; + + /** The Constant LOGGER. */ + private static final Logger LOGGER = Logger + .getLogger(CreateUpdateChangeSetTestCase.class.getName()); + + /** The Constant cafeCity. */ + private static final String cafeCity = "TestCity"; + + /** The Constant cafeNameUpdated. */ + private static final String cafeNameUpdated = "TestName-updated"; + + /** Inner component. */ + private Component component = new Component(); + + /** OData service used for all tests. */ + @SuppressWarnings("unused") + private CafeService service; + + /* + * (non-Javadoc) + * + * @see org.restlet.test.RestletTestCase#setUp() + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + + component.getServers().add(Protocol.HTTP, 8111); + component.getClients().add(Protocol.CLAP); + component.getDefaultHost().attach("/Cafe.svc", + new CafeCrudApplication()); + component.start(); + + service = new CafeService(); + } + + /* + * (non-Javadoc) + * + * @see org.restlet.test.RestletTestCase#tearDown() + */ + @Override + protected void tearDown() throws Exception { + component.stop(); + component = null; + super.tearDown(); + } + + /** + * Test method for CREATE & UPDATE operation on simple entities. + */ + public void testCreateUpdate() { + CafeService service = new CafeService(); + + // create. + Cafe cafe = new Cafe(); + cafe.setId(cafeId); + cafe.setName(cafeName); + cafe.setCity(cafeCity); + cafe.setZipCode(cafeZipCode); + + // update + Cafe cafeU = new Cafe(); + cafe.setName(cafeNameUpdated); + try { + + BatchRequest br = service.createBatchRequest(); + ChangeSetRequestImpl changeSetRequest = new ChangeSetRequestImpl(); + // Create request + CreateEntityRequest createEntityRequest = new CreateEntityRequest( + service, cafe); + UpdateEntityRequest updateEntityRequest = new UpdateEntityRequest( + service, cafeU); + changeSetRequest.addRequest(createEntityRequest); + changeSetRequest.addRequest(updateEntityRequest); + List responses = br.addRequest(changeSetRequest) + .execute(); + for (BatchResponse batchResponse : responses) { + batchResponse.getEntity(); + dumpResponse(responses); + } + + // Assert for response. + Query createquery = service.createCafeQuery("/Cafes"); + Cafe cafe1 = createquery.iterator().next(); + assertEquals(cafeName, cafe1.getName()); + assertEquals(cafeId, cafe1.getId()); + assertEquals(cafeZipCode, cafe1.getZipCode()); + Response latestResponse = createquery.getService() + .getLatestResponse(); + latestResponse = createquery.getService().getLatestResponse(); + assertTrue(latestResponse.getStatus().isSuccess()); + Query updatequery = service.updateCafeQuery("/Cafes('40')"); + latestResponse = updatequery.getService().getLatestResponse(); + assertEquals(Status.SUCCESS_OK, latestResponse.getStatus()); + } catch (Exception ex) { + LOGGER.log(Level.SEVERE, ex.getMessage()); + Assert.fail(); + } + } + + /** + * Dump response. + * + * @param responses + * the responses + */ + @SuppressWarnings("unchecked") + public static void dumpResponse(List responses) { + for (BatchResponse batchResponse : responses) { + Object entity = batchResponse.getEntity(); + if (batchResponse instanceof ChangeSetResponse) { + LOGGER.info("Dumping changeset"); + dumpResponse((List) entity); + LOGGER.info("Done with changeset"); + } else { + LOGGER.info("Status =" + batchResponse.getStatus()); + LOGGER.info("Entity = " + entity); + MultivaluedMap headers = batchResponse + .getHeaders(); + if (headers != null) { + Set keySet = headers.keySet(); + LOGGER.info("Headers : "); + for (String key : keySet) { + List value = headers.get(key); + LOGGER.info("Key =" + key + "/t" + "value = " + value); + } + } + } + } + } + +} \ No newline at end of file diff --git a/modules/org.restlet.test/src/org/restlet/test/batch/crud/DeleteCafeTestCase.java b/modules/org.restlet.test/src/org/restlet/test/batch/crud/DeleteCafeTestCase.java new file mode 100644 index 0000000000..1418192eb3 --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/batch/crud/DeleteCafeTestCase.java @@ -0,0 +1,152 @@ +package org.restlet.test.batch.crud; + +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.ws.rs.core.MultivaluedMap; + +import junit.framework.Assert; + +import org.restlet.Component; +import org.restlet.Response; +import org.restlet.data.Protocol; +import org.restlet.ext.odata.Query; +import org.restlet.ext.odata.batch.request.BatchRequest; +import org.restlet.ext.odata.batch.request.impl.ChangeSetRequestImpl; +import org.restlet.ext.odata.batch.request.impl.DeleteEntityRequest; +import org.restlet.ext.odata.batch.response.BatchResponse; +import org.restlet.ext.odata.batch.response.ChangeSetResponse; +import org.restlet.test.RestletTestCase; +import org.restlet.test.ext.odata.crud.Cafe; + +/** + * Test case for RestletBatch service for DELETE operation on entities. + */ +public class DeleteCafeTestCase extends RestletTestCase { + + /** The Constant cafeName. */ + private static final String cafeName = "TestName"; + + /** The Constant cafeId. */ + private static final String cafeId = "40"; + + /** The Constant cafeZipCode. */ + private static final int cafeZipCode = 111111; + + /** The Constant LOGGER. */ + private static final Logger LOGGER = Logger + .getLogger(DeleteCafeTestCase.class.getName()); + + /** The Constant cafeCity. */ + private static final String cafeCity = "TestCity"; + + /** Inner component. */ + private Component component = new Component(); + + /** OData service used for all tests. */ + @SuppressWarnings("unused") + private CafeService service; + + /* + * (non-Javadoc) + * + * @see org.restlet.test.RestletTestCase#setUp() + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + + component.getServers().add(Protocol.HTTP, 8111); + component.getClients().add(Protocol.CLAP); + component.getDefaultHost().attach("/Cafe.svc", + new CafeCrudApplication()); + component.start(); + + service = new CafeService(); + } + + /* + * (non-Javadoc) + * + * @see org.restlet.test.RestletTestCase#tearDown() + */ + @Override + protected void tearDown() throws Exception { + component.stop(); + component = null; + super.tearDown(); + } + + /** + * Test method for DELETE operation on simple entities. + */ + public void testDelete() { + CafeService service = new CafeService(); + + // create. + Cafe cafe = new Cafe(); + cafe.setId(cafeId); + cafe.setName(cafeName); + cafe.setCity(cafeCity); + cafe.setZipCode(cafeZipCode); + + // delete + Cafe cafeD = new Cafe(); + cafeD.setId(cafeId); + cafeD.setName(cafeName); + cafeD.setCity("TestCity"); + cafeD.setZipCode(cafeZipCode); + try { + BatchRequest br = service.createBatchRequest(); + ChangeSetRequestImpl changeSetRequest = new ChangeSetRequestImpl(); + // Create request + DeleteEntityRequest deleteEntityRequest = new DeleteEntityRequest( + service, cafe); + changeSetRequest.addRequest(deleteEntityRequest); + List responses = br.addRequest(changeSetRequest) + .execute(); + dumpResponse(responses); + Query deleteQuery = service.deleteCafeQuery("/Cafes('40')"); + Response latestResponse = deleteQuery.getService() + .getLatestResponse(); + assertTrue(latestResponse.getStatus().isSuccess()); + } catch (Exception ex) { + LOGGER.log(Level.SEVERE, ex.getMessage()); + Assert.fail(); + } + } + + /** + * Dump response. + * + * @param responses + * the responses + */ + @SuppressWarnings("unchecked") + public static void dumpResponse(List responses) { + for (BatchResponse batchResponse : responses) { + Object entity = batchResponse.getEntity(); + if (batchResponse instanceof ChangeSetResponse) { + LOGGER.info("Dumping changeset"); + dumpResponse((List) entity); + LOGGER.info("Done with changeset"); + } else { + LOGGER.info("Status =" + batchResponse.getStatus()); + LOGGER.info("Entity = " + entity); + MultivaluedMap headers = batchResponse + .getHeaders(); + if (headers != null) { + Set keySet = headers.keySet(); + LOGGER.info("Headers : "); + for (String key : keySet) { + List value = headers.get(key); + LOGGER.info("Key =" + key + "/t" + "value = " + value); + } + } + } + } + } + +} \ No newline at end of file diff --git a/modules/org.restlet.test/src/org/restlet/test/batch/crud/GetCafeTestCase.java b/modules/org.restlet.test/src/org/restlet/test/batch/crud/GetCafeTestCase.java new file mode 100644 index 0000000000..3c635715f9 --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/batch/crud/GetCafeTestCase.java @@ -0,0 +1,149 @@ +package org.restlet.test.batch.crud; + +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.ws.rs.core.MultivaluedMap; + +import junit.framework.Assert; + +import org.restlet.Component; +import org.restlet.Response; +import org.restlet.data.Protocol; +import org.restlet.ext.odata.Query; +import org.restlet.ext.odata.batch.request.BatchRequest; +import org.restlet.ext.odata.batch.request.impl.GetEntityRequest; +import org.restlet.ext.odata.batch.response.BatchResponse; +import org.restlet.ext.odata.batch.response.ChangeSetResponse; +import org.restlet.test.RestletTestCase; +import org.restlet.test.ext.odata.crud.Cafe; + +/** + * Test case for RestletBatch service for GET operation on entities. + */ +public class GetCafeTestCase extends RestletTestCase { + + /** The Constant cafeName. */ + private static final String cafeName = "TestName"; + + /** The Constant cafeId. */ + private static final String cafeId = "40"; + + /** The Constant cafeZipCode. */ + private static final int cafeZipCode = 111111; + + /** The Constant LOGGER. */ + private static final Logger LOGGER = Logger.getLogger(GetCafeTestCase.class + .getName()); + + /** The Constant cafeCity. */ + private static final String cafeCity = "TestCity"; + + /** Inner component. */ + private Component component = new Component(); + + /** OData service used for all tests. */ + @SuppressWarnings("unused") + private CafeService service; + + /* + * (non-Javadoc) + * + * @see org.restlet.test.RestletTestCase#setUp() + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + + component.getServers().add(Protocol.HTTP, 8111); + component.getClients().add(Protocol.CLAP); + component.getDefaultHost().attach("/Cafe.svc", + new CafeCrudApplication()); + component.start(); + + service = new CafeService(); + } + + /* + * (non-Javadoc) + * + * @see org.restlet.test.RestletTestCase#tearDown() + */ + @Override + protected void tearDown() throws Exception { + component.stop(); + component = null; + super.tearDown(); + } + + /** + * Test method for GET operation on simple entities. + */ + public void testGet() { + + // Get + CafeService service = new CafeService(); + Cafe cafeG = new Cafe(); + cafeG.setId(cafeId); + cafeG.setName(cafeName); + cafeG.setCity(cafeCity); + cafeG.setZipCode(cafeZipCode); + try { + BatchRequest br = service.createBatchRequest(); + // get request + Query getQuery = service.createQuery("/Cafes('40')", + Cafe.class); + GetEntityRequest getEntityRequest = new GetEntityRequest(getQuery); + List responses = br.addRequest(getEntityRequest) + .execute(); + dumpResponse(responses); + Assert.assertTrue(true); + // Assert for response. + Query getquery = service.getCafeQuery("/Cafes"); + Cafe cafe2 = getquery.iterator().next(); + assertEquals(cafeName, cafe2.getName()); + assertEquals(cafeId, cafe2.getId()); + assertEquals(cafeZipCode, cafe2.getZipCode()); + Response latestResponse = getQuery.getService().getLatestResponse(); + latestResponse = getquery.getService().getLatestResponse(); + assertTrue(latestResponse.getStatus().isSuccess()); + } catch (Exception ex) { + LOGGER.log(Level.SEVERE, ex.getMessage()); + Assert.fail(); + } + } + + /** + * Dump response. + * + * @param responses + * the responses + */ + @SuppressWarnings("unchecked") + public static void dumpResponse(List responses) { + for (BatchResponse batchResponse : responses) { + Object entity = batchResponse.getEntity(); + if (batchResponse instanceof ChangeSetResponse) { + LOGGER.info("Dumping changeset"); + dumpResponse((List) entity); + LOGGER.info("Done with changeset"); + } else { + LOGGER.info("Status =" + batchResponse.getStatus()); + LOGGER.info("Entity = " + entity); + MultivaluedMap headers = batchResponse + .getHeaders(); + if (headers != null) { + Set keySet = headers.keySet(); + LOGGER.info("Headers : "); + for (String key : keySet) { + List value = headers.get(key); + LOGGER.info("Key =" + key + "/t" + "value = " + value); + } + } + } + } + } + +} \ No newline at end of file diff --git a/modules/org.restlet.test/src/org/restlet/test/batch/crud/MultipleChangeSetTestCase.java b/modules/org.restlet.test/src/org/restlet/test/batch/crud/MultipleChangeSetTestCase.java new file mode 100644 index 0000000000..761927ae55 --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/batch/crud/MultipleChangeSetTestCase.java @@ -0,0 +1,190 @@ +package org.restlet.test.batch.crud; + +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.ws.rs.core.MultivaluedMap; + +import junit.framework.Assert; + +import org.restlet.Component; +import org.restlet.Response; +import org.restlet.data.Protocol; +import org.restlet.data.Status; +import org.restlet.ext.odata.Query; +import org.restlet.ext.odata.batch.request.BatchRequest; +import org.restlet.ext.odata.batch.request.impl.ChangeSetRequestImpl; +import org.restlet.ext.odata.batch.request.impl.CreateEntityRequest; +import org.restlet.ext.odata.batch.request.impl.DeleteEntityRequest; +import org.restlet.ext.odata.batch.request.impl.UpdateEntityRequest; +import org.restlet.ext.odata.batch.response.BatchResponse; +import org.restlet.ext.odata.batch.response.ChangeSetResponse; +import org.restlet.test.RestletTestCase; +import org.restlet.test.ext.odata.crud.Cafe; + +/** + * The Class MultipleChangeSetTestCase. + * + * Batch + * ---changeset1 :Create + * ---changeset2 :Update + * ---changeset3 :Delete + * Batch ends + */ + +public class MultipleChangeSetTestCase extends RestletTestCase { + + /** The Constant cafeName. */ + private static final String cafeName = "TestName"; + + /** The Constant cafeId. */ + private static final String cafeId = "40"; + + /** The Constant cafeZipCode. */ + private static final int cafeZipCode = 111111; + + /** The Constant LOGGER. */ + private static final Logger LOGGER = Logger + .getLogger(MultipleChangeSetTestCase.class.getName()); + + /** The Constant cafeCity. */ + private static final String cafeCity = "TestCity"; + + /** The Constant cafeNameUpdated. */ + private static final String cafeNameUpdated = "TestName-updated"; + + /** Inner component. */ + private Component component = new Component(); + + /** OData service used for all tests. */ + @SuppressWarnings("unused") + private CafeService service; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + component.getServers().add(Protocol.HTTP, 8111); + component.getClients().add(Protocol.CLAP); + component.getDefaultHost().attach("/Cafe.svc", + new CafeCrudApplication()); + component.start(); + + service = new CafeService(); + } + + /* + * (non-Javadoc) + * + * @see org.restlet.test.RestletTestCase#tearDown() + */ + @Override + protected void tearDown() throws Exception { + component.stop(); + component = null; + super.tearDown(); + } + + /** + * Test multiple change set. + */ + public void testMultipleChangeSet() { + CafeService service = new CafeService(); + + // create & delete + Cafe cafe = new Cafe(); + cafe.setId(cafeId); + cafe.setName(cafeName); + cafe.setCity(cafeCity); + cafe.setZipCode(cafeZipCode); + + // create & update + Cafe cafeU = new Cafe(); + cafe.setName(cafeNameUpdated); + + try { + + BatchRequest br = service.createBatchRequest(); + ChangeSetRequestImpl changeSetRequest1 = new ChangeSetRequestImpl(); + CreateEntityRequest createEntityRequest = new CreateEntityRequest( + service, cafe); + changeSetRequest1.addRequest(createEntityRequest); + ChangeSetRequestImpl changeSetRequest2 = new ChangeSetRequestImpl(); + UpdateEntityRequest updateEntityRequest = new UpdateEntityRequest( + service, cafeU); + changeSetRequest2.addRequest(updateEntityRequest); + ChangeSetRequestImpl changeSetRequest3 = new ChangeSetRequestImpl(); + DeleteEntityRequest deleteEntityRequest = new DeleteEntityRequest( + service, cafe); + changeSetRequest3.addRequest(deleteEntityRequest); + List responses = br.addRequest(changeSetRequest1) + .addRequest(changeSetRequest2) + .addRequest(changeSetRequest3).execute(); + dumpResponse(responses); + + // Assert for response-create & delete + Query createquery = service.createCafeQuery("/Cafes"); + Cafe cafe1 = createquery.iterator().next(); + assertEquals(cafeName, cafe1.getName()); + assertEquals(cafeId, cafe1.getId()); + assertEquals(cafeZipCode, cafe1.getZipCode()); + Response latestResponse = createquery.getService() + .getLatestResponse(); + latestResponse = createquery.getService().getLatestResponse(); + assertTrue(latestResponse.getStatus().isSuccess()); + Query deletequery = service.deleteCafeQuery(("/Cafes('40')")); + latestResponse = deletequery.getService().getLatestResponse(); + assertEquals(Status.SUCCESS_OK, latestResponse.getStatus()); + + // Assert for response-create & update + Query createquery2 = service.createCafeQuery("/Cafes"); + Cafe cafe2 = createquery2.iterator().next(); + assertEquals(cafeName, cafe2.getName()); + assertEquals(cafeId, cafe2.getId()); + assertEquals(cafeZipCode, cafe2.getZipCode()); + latestResponse = createquery.getService().getLatestResponse(); + latestResponse = createquery.getService().getLatestResponse(); + assertTrue(latestResponse.getStatus().isSuccess()); + Query updatequery = service.updateCafeQuery("/Cafes('40')"); + latestResponse = updatequery.getService().getLatestResponse(); + assertEquals(Status.SUCCESS_OK, latestResponse.getStatus()); + } catch (Exception ex) { + LOGGER.log(Level.SEVERE, ex.getMessage()); + Assert.fail(); + } + } + + /** + * Dump response. + * + * @param responses + * the responses + */ + @SuppressWarnings("unchecked") + public static void dumpResponse(List responses) { + for (BatchResponse batchResponse : responses) { + Object entity = batchResponse.getEntity(); + if (batchResponse instanceof ChangeSetResponse) { + LOGGER.info("Dumping changeset"); + dumpResponse((List) entity); + LOGGER.info("Done with changeset"); + } else { + LOGGER.info("Status =" + batchResponse.getStatus()); + LOGGER.info("Entity = " + entity); + MultivaluedMap headers = batchResponse + .getHeaders(); + if (headers != null) { + Set keySet = headers.keySet(); + LOGGER.info("Headers : "); + for (String key : keySet) { + List value = headers.get(key); + LOGGER.info("Key =" + key + "/t" + "value = " + value); + } + } + } + } + } + +} \ No newline at end of file diff --git a/modules/org.restlet.test/src/org/restlet/test/batch/crud/MultipleChangeSetWithGetTestCase.java b/modules/org.restlet.test/src/org/restlet/test/batch/crud/MultipleChangeSetWithGetTestCase.java new file mode 100644 index 0000000000..144f0a5c05 --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/batch/crud/MultipleChangeSetWithGetTestCase.java @@ -0,0 +1,186 @@ +package org.restlet.test.batch.crud; + +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.ws.rs.core.MultivaluedMap; + +import junit.framework.Assert; + +import org.restlet.Component; +import org.restlet.Response; +import org.restlet.data.Protocol; +import org.restlet.data.Status; +import org.restlet.ext.odata.Query; +import org.restlet.ext.odata.batch.request.BatchRequest; +import org.restlet.ext.odata.batch.request.impl.ChangeSetRequestImpl; +import org.restlet.ext.odata.batch.request.impl.CreateEntityRequest; +import org.restlet.ext.odata.batch.request.impl.DeleteEntityRequest; +import org.restlet.ext.odata.batch.request.impl.GetEntityRequest; +import org.restlet.ext.odata.batch.request.impl.UpdateEntityRequest; +import org.restlet.ext.odata.batch.response.BatchResponse; +import org.restlet.ext.odata.batch.response.ChangeSetResponse; +import org.restlet.test.RestletTestCase; +import org.restlet.test.ext.odata.crud.Cafe; + +/** + * The Class MultipleChangeSetWithGetTestCase. + * Batch--- + * ---Get entity + * ---changeset1 :Create + * ---changeset2 :Update + * ---changeset3 :Delete + * Batch ends + * + */ +public class MultipleChangeSetWithGetTestCase extends RestletTestCase { + + /** The Constant cafeName. */ + private static final String cafeName = "TestName"; + + /** The Constant cafeId. */ + private static final String cafeId = "40"; + + /** The Constant cafeZipCode. */ + private static final int cafeZipCode = 111111; + + /** The Constant LOGGER. */ + private static final Logger LOGGER = Logger.getLogger(MultipleChangeSetTestCase.class.getName()); + + /** The Constant cafeCity. */ + private static final String cafeCity = "TestCity"; + + /** The Constant cafeNameUpdated. */ + private static final String cafeNameUpdated = "TestName-updated"; + + /** Inner component. */ + private Component component = new Component(); + + + /** OData service used for all tests. */ + @SuppressWarnings("unused") + private CafeService service; + + /* (non-Javadoc) + * @see org.restlet.test.RestletTestCase#setUp() + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + + component.getServers().add(Protocol.HTTP, 8111); + component.getClients().add(Protocol.CLAP); + component.getDefaultHost().attach("/Cafe.svc", + new CafeCrudApplication()); + component.start(); + + service = new CafeService(); + } + + + @Override + protected void tearDown() throws Exception { + component.stop(); + component = null; + super.tearDown(); + } + + /** + * Test multiple operations with get. + */ + public void testMultipleOperationsWithGet() { + CafeService service = new CafeService(); + + // create & delete + Cafe cafe = new Cafe(); + cafe.setId(cafeId); + cafe.setName(cafeName); + cafe.setCity(cafeCity); + cafe.setZipCode(cafeZipCode); + + //create & update + Cafe cafeU = new Cafe(); + cafe.setName(cafeNameUpdated); + + try { + + BatchRequest br = service.createBatchRequest(); + Query getQuery = service.createQuery("/Cafes('40')",Cafe.class); + GetEntityRequest getEntityRequest = new GetEntityRequest(getQuery); + ChangeSetRequestImpl changeSetRequest1 = new ChangeSetRequestImpl(); + CreateEntityRequest createEntityRequest = new CreateEntityRequest(service,cafe); + changeSetRequest1.addRequest(createEntityRequest); + ChangeSetRequestImpl changeSetRequest2 = new ChangeSetRequestImpl(); + UpdateEntityRequest updateEntityRequest = new UpdateEntityRequest(service, cafeU); + changeSetRequest2.addRequest(updateEntityRequest); + ChangeSetRequestImpl changeSetRequest3 = new ChangeSetRequestImpl(); + DeleteEntityRequest deleteEntityRequest = new DeleteEntityRequest(service, cafe); + changeSetRequest3.addRequest(deleteEntityRequest); + List responses = br.addRequest(getEntityRequest).addRequest(changeSetRequest1).addRequest(changeSetRequest2).addRequest(changeSetRequest3).execute(); + dumpResponse(responses); + + + //Assert for response-create & delete + Query createquery = service.createCafeQuery("/Cafes"); + Cafe cafe1=createquery.iterator().next(); + assertEquals(cafeName, cafe1.getName()); + assertEquals(cafeId, cafe1.getId()); + assertEquals(cafeZipCode, cafe1.getZipCode()); + Response latestResponse = createquery.getService().getLatestResponse(); + latestResponse = createquery.getService().getLatestResponse(); + assertTrue(latestResponse.getStatus().isSuccess()); + Query deletequery = service.deleteCafeQuery(("/Cafes('40')")); + latestResponse = deletequery.getService().getLatestResponse(); + assertEquals(Status.SUCCESS_OK,latestResponse.getStatus()); + + //Assert for response-create & update + Query createquery2 = service.createCafeQuery("/Cafes"); + Cafe cafe2=createquery2.iterator().next(); + assertEquals(cafeName, cafe2.getName()); + assertEquals(cafeId, cafe2.getId()); + assertEquals(cafeZipCode, cafe2.getZipCode()); + latestResponse = createquery.getService().getLatestResponse(); + latestResponse = createquery.getService().getLatestResponse(); + assertTrue(latestResponse.getStatus().isSuccess()); + Query updatequery = service.updateCafeQuery("/Cafes('40')"); + latestResponse = updatequery.getService().getLatestResponse(); + assertEquals(Status.SUCCESS_OK,latestResponse.getStatus()); + } catch (Exception ex) { + LOGGER.log(Level.SEVERE, ex.getMessage()); + Assert.fail(); + } + } + + /** + * Dump response. + * + * @param responses the responses + */ + @SuppressWarnings("unchecked") + public static void dumpResponse(List responses) { + for (BatchResponse batchResponse : responses) { + Object entity = batchResponse.getEntity(); + if(batchResponse instanceof ChangeSetResponse){ + LOGGER.info("Dumping changeset"); + dumpResponse((List)entity); + LOGGER.info("Done with changeset"); + }else{ + LOGGER.info("Status ="+ batchResponse.getStatus()); + LOGGER.info("Entity = "+ entity); + MultivaluedMap headers = batchResponse.getHeaders(); + if(headers!=null){ + Set keySet = headers.keySet(); + LOGGER.info("Headers : "); + for (String key : keySet) { + List value = headers.get(key); + LOGGER.info("Key ="+ key + "/t"+"value = "+ value); + } + } + } + } + } + + +} \ No newline at end of file diff --git a/modules/org.restlet.test/src/org/restlet/test/batch/crud/UpdateCafeTestCase.java b/modules/org.restlet.test/src/org/restlet/test/batch/crud/UpdateCafeTestCase.java new file mode 100644 index 0000000000..9be293814e --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/batch/crud/UpdateCafeTestCase.java @@ -0,0 +1,149 @@ +package org.restlet.test.batch.crud; + +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.ws.rs.core.MultivaluedMap; + +import junit.framework.Assert; + +import org.restlet.Component; +import org.restlet.Response; +import org.restlet.data.Protocol; +import org.restlet.ext.odata.Query; +import org.restlet.ext.odata.batch.request.BatchRequest; +import org.restlet.ext.odata.batch.request.impl.ChangeSetRequestImpl; +import org.restlet.ext.odata.batch.request.impl.UpdateEntityRequest; +import org.restlet.ext.odata.batch.response.BatchResponse; +import org.restlet.ext.odata.batch.response.ChangeSetResponse; +import org.restlet.test.RestletTestCase; +import org.restlet.test.ext.odata.crud.Cafe; + +/** + * Test case for RestletBatch service for UPDATE operation on entities. + * + */ +public class UpdateCafeTestCase extends RestletTestCase { + + + /** The Constant cafeName. */ + private static final String cafeName = "TestName"; + + /** The Constant cafeNameUpdated. */ + private static final String cafeNameUpdated = "TestName-updated"; + + /** The Constant cafeId. */ + private static final String cafeId = "40"; + + /** The Constant cafeZipCode. */ + private static final int cafeZipCode = 111111; + + /** The Constant LOGGER. */ + private static final Logger LOGGER = Logger.getLogger(UpdateCafeTestCase.class.getName()); + + /** The Constant cafeCity. */ + private static final String cafeCity = "TestCity"; + + /** Inner component. */ + private Component component = new Component(); + + + /** OData service used for all tests. */ + @SuppressWarnings("unused") + private CafeService service; + + /* (non-Javadoc) + * @see org.restlet.test.RestletTestCase#setUp() + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + + component.getServers().add(Protocol.HTTP, 8111); + component.getClients().add(Protocol.CLAP); + component.getDefaultHost().attach("/Cafe.svc", + new CafeCrudApplication()); + component.start(); + + service = new CafeService(); + } + + /* (non-Javadoc) + * @see org.restlet.test.RestletTestCase#tearDown() + */ + @Override + protected void tearDown() throws Exception { + component.stop(); + component = null; + super.tearDown(); + } + + /** + * Test method for UPDATE operation on simple entities. + */ + public void testUpdate() { + CafeService service = new CafeService(); + Cafe cafe = new Cafe(); + cafe.setId(cafeId); + cafe.setName(cafeName); + cafe.setCity(cafeCity); + cafe.setZipCode(cafeZipCode); + + + //update + Cafe cafeU = new Cafe(); + cafeU.setId(cafeId); + cafeU.setName(cafeName); + cafeU.setCity(cafeCity); + cafeU.setZipCode(cafeZipCode); + cafeU.setName(cafeNameUpdated); + try { + BatchRequest br = service.createBatchRequest(); + ChangeSetRequestImpl changeSetRequest = new ChangeSetRequestImpl(); + UpdateEntityRequest updateEntityRequest = new UpdateEntityRequest(service,cafe); + changeSetRequest.addRequest(updateEntityRequest); + List responses = br.addRequest(changeSetRequest).execute(); + dumpResponse(responses); + + Query updatequery = service.updateCafeQuery("/Cafes('40')"); + Response latestResponse = updatequery.getService().getLatestResponse(); + assertTrue(latestResponse.getStatus().isSuccess()); + } catch (Exception ex) { + LOGGER.log(Level.SEVERE, ex.getMessage()); + Assert.fail(); + } + } + + /** + * Dump response. + * + * @param responses the responses + */ + @SuppressWarnings("unchecked") + public static void dumpResponse(List responses) { + for (BatchResponse batchResponse : responses) { + Object entity = batchResponse.getEntity(); + if(batchResponse instanceof ChangeSetResponse){ + LOGGER.info("Dumping changeset"); + dumpResponse((List)entity); + LOGGER.info("Done with changeset"); + }else{ + LOGGER.info("Status ="+ batchResponse.getStatus()); + LOGGER.info("Entity = "+ entity); + MultivaluedMap headers = batchResponse.getHeaders(); + if(headers!=null){ + Set keySet = headers.keySet(); + LOGGER.info("Headers : "); + for (String key : keySet) { + List value = headers.get(key); + LOGGER.info("Key ="+ key + "/t"+"value = "+ value); + } + } + } + } + } + + +} \ No newline at end of file diff --git a/modules/org.restlet.test/src/org/restlet/test/batch/crud/UpdateDeleteChangeSetTestCase.java b/modules/org.restlet.test/src/org/restlet/test/batch/crud/UpdateDeleteChangeSetTestCase.java new file mode 100644 index 0000000000..b8a98e479e --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/batch/crud/UpdateDeleteChangeSetTestCase.java @@ -0,0 +1,160 @@ +package org.restlet.test.batch.crud; + +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.ws.rs.core.MultivaluedMap; + +import junit.framework.Assert; + +import org.restlet.Component; +import org.restlet.Response; +import org.restlet.data.Protocol; +import org.restlet.data.Status; +import org.restlet.ext.odata.Query; +import org.restlet.ext.odata.batch.request.BatchRequest; +import org.restlet.ext.odata.batch.request.impl.ChangeSetRequestImpl; +import org.restlet.ext.odata.batch.request.impl.DeleteEntityRequest; +import org.restlet.ext.odata.batch.request.impl.UpdateEntityRequest; +import org.restlet.ext.odata.batch.response.BatchResponse; +import org.restlet.ext.odata.batch.response.ChangeSetResponse; +import org.restlet.test.RestletTestCase; +import org.restlet.test.ext.odata.crud.Cafe; + +/** + * Test case for RestletBatch service for UPDATE & DELETE operation on entities. + * + */ +public class UpdateDeleteChangeSetTestCase extends RestletTestCase { + + + /** The Constant cafeName. */ + private static final String cafeName = "TestName"; + + /** The Constant cafeId. */ + private static final String cafeId = "40"; + + /** The Constant cafeZipCode. */ + private static final int cafeZipCode = 111111; + + /** The Constant LOGGER. */ + private static final Logger LOGGER = Logger.getLogger(UpdateDeleteChangeSetTestCase.class.getName()); + + /** The Constant cafeCity. */ + private static final String cafeCity = "TestCity"; + + /** The Constant cafeNameUpdated. */ + private static final String cafeNameUpdated = "TestName-updated"; + + /** Inner component. */ + private Component component = new Component(); + + + /** OData service used for all tests. */ + @SuppressWarnings("unused") + private CafeService service; + + /* (non-Javadoc) + * @see org.restlet.test.RestletTestCase#setUp() + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + + component.getServers().add(Protocol.HTTP, 8111); + component.getClients().add(Protocol.CLAP); + component.getDefaultHost().attach("/Cafe.svc", + new CafeCrudApplication()); + component.start(); + + service = new CafeService(); + } + + /* (non-Javadoc) + * @see org.restlet.test.RestletTestCase#tearDown() + */ + @Override + protected void tearDown() throws Exception { + component.stop(); + component = null; + super.tearDown(); + } + + /** + * Test method for UPDATE & DELETE operation on simple entities. + */ + public void testUpdateDelete() { + CafeService service = new CafeService(); + + // Update & Delete + Cafe cafe = new Cafe(); + cafe.setId(cafeId); + cafe.setName(cafeName); + cafe.setCity(cafeCity); + cafe.setZipCode(cafeZipCode); + + + //update + Cafe cafeU = new Cafe(); + cafe.setName(cafeNameUpdated); + try { + + BatchRequest br = service.createBatchRequest(); + ChangeSetRequestImpl changeSetRequest = new ChangeSetRequestImpl(); + UpdateEntityRequest updateEntityRequest = new UpdateEntityRequest(service, cafeU); + DeleteEntityRequest deleteEntityRequest = new DeleteEntityRequest(service, cafe); + changeSetRequest.addRequest(updateEntityRequest); + changeSetRequest.addRequest(deleteEntityRequest); + List responses = br.addRequest(changeSetRequest).execute(); + for (BatchResponse batchResponse : responses) { + batchResponse.getEntity(); + dumpResponse(responses); + } + + //Assert for response. + Query createquery = service.createCafeQuery("/Cafes('40')"); + Response latestResponse = createquery.getService().getLatestResponse(); + latestResponse = createquery.getService().getLatestResponse(); + assertTrue(latestResponse.getStatus().isSuccess()); + Query updatequery = service.updateCafeQuery("/Cafes('40')"); + latestResponse = updatequery.getService().getLatestResponse(); + assertEquals(Status.SUCCESS_OK,latestResponse.getStatus()); + } catch (Exception ex) { + LOGGER.log(Level.SEVERE, ex.getMessage()); + Assert.fail(); + } + } + + /** + * Dump response. + * + * @param responses the responses + */ + @SuppressWarnings("unchecked") + public static void dumpResponse(List responses) { + for (BatchResponse batchResponse : responses) { + Object entity = batchResponse.getEntity(); + if(batchResponse instanceof ChangeSetResponse){ + LOGGER.info("Dumping changeset"); + dumpResponse((List)entity); + LOGGER.info("Done with changeset"); + }else{ + LOGGER.info("Status ="+ batchResponse.getStatus()); + LOGGER.info("Entity = "+ entity); + MultivaluedMap headers = batchResponse.getHeaders(); + if(headers!=null){ + Set keySet = headers.keySet(); + LOGGER.info("Headers : "); + for (String key : keySet) { + List value = headers.get(key); + LOGGER.info("Key ="+ key + "/t"+"value = "+ value); + } + } + } + } + } + + +} \ No newline at end of file diff --git a/modules/org.restlet.test/src/org/restlet/test/batch/xml/cafes.xml b/modules/org.restlet.test/src/org/restlet/test/batch/xml/cafes.xml new file mode 100644 index 0000000000..b7e7f07c33 --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/batch/xml/cafes.xml @@ -0,0 +1,29 @@ + + + Cafes + http://localhost:8111/Cafe.svc/Cafes + 2010-02-17T11:28:13Z + + + http://localhost:8111/Cafe.svc/Cafes('40') + + 2010-02-17T11:28:13Z + + + + + + + + 40 + TestName + 111111 + TestCity + + + + \ No newline at end of file diff --git a/modules/org.restlet.test/src/org/restlet/test/batch/xml/cafesUpdatedRequest.xml b/modules/org.restlet.test/src/org/restlet/test/batch/xml/cafesUpdatedRequest.xml new file mode 100644 index 0000000000..009ccffbbb --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/batch/xml/cafesUpdatedRequest.xml @@ -0,0 +1,29 @@ + + + Cafes + http://localhost:8111/Cafe.svc/Cafes + 2010-02-17T11:28:13Z + + + http://localhost:8111/Cafe.svc/Cafes('40') + + 2010-02-17T11:28:13Z + + + + + + + + 40 + TestName-updated + 111111 + TestCity + + + + \ No newline at end of file diff --git a/modules/org.restlet.test/src/org/restlet/test/batch/xml/createCafeResponse.xml b/modules/org.restlet.test/src/org/restlet/test/batch/xml/createCafeResponse.xml new file mode 100644 index 0000000000..fb68d6f0ed --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/batch/xml/createCafeResponse.xml @@ -0,0 +1,24 @@ + + +HTTP/1.1 201 Created +Content-Type: application/atom+xml;charset=utf-8 +Location: http://localhost:8111/Cafe.svc/Cafes('40') +DataServiceVersion: 3.0 + + +http://localhost:8111/Cafe.svc/Cafes('40') +<updated>2014-05-27T09:17:16Z</updated><author><name/></author><link rel="edit" title="Cafes" href="Cafes('40')"/><link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Cafes" type="application/atom+xml;type=feed" title="Cafes" href="Cafes('40')/Cafes"/><category term="Cafes" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme"/> +<content type="application/xml"> +<m:properties><d:ref_value_status m:null="true"/> +<d:tool_parm_name>40</d:tool_parm_name> +<d:unit_type_id>UNKNOWN</d:unit_type_id> +<d:create_date m:type="Edm.DateTime">2014-06-23T09:44:08</d:create_date> +<d:update_date m:type="Edm.DateTime" m:null="true"/><d:ref_value_source m:null="true"/> +<d:create_user_id>OWAT</d:create_user_id><d:update_user_id m:null="true"/> +<d:City m:type="Edm.String">TestCity</d:City> +<d:ID m:type="Edm.String">40</d:ID> +<d:Name m:type="Edm.String">TestName</d:Name> +<d:ZipCode m:type="Edm.Int32">111111</d:ZipCode> +<d:tool_parm_id m:type="Edm.Int32">221</d:tool_parm_id> +</m:properties></content></entry> \ No newline at end of file diff --git a/modules/org.restlet.test/src/org/restlet/test/batch/xml/deleteCafeResponse.xml b/modules/org.restlet.test/src/org/restlet/test/batch/xml/deleteCafeResponse.xml new file mode 100644 index 0000000000..507d35844b --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/batch/xml/deleteCafeResponse.xml @@ -0,0 +1,24 @@ + + +HTTP/1.1 200 OK +Content-Type: application/atom+xml;charset=utf-8 +Location: http://localhost:8111/Cafe.svc/Cafes('40') +DataServiceVersion: 3.0 + +<?xml version='1.0' encoding='utf-8'?><entry xmlns="http://www.w3.org/2005/Atom" +xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xml:base="http://134.132.100.20:8080/dsdataserver/dsl.svc/"> +<id>http://localhost:8111/Cafe.svc/Cafes('40')</id> +<title type="text"/><updated>2014-05-27T09:17:16Z</updated><author><name/></author><link rel="edit" title="Cafes" href="Cafes('40')"/><link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Cafes" type="application/atom+xml;type=feed" title="Cafes" href="Cafes('40')/Cafes"/><category term="Cafes" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme"/> +<content type="application/xml"> +<m:properties><d:ref_value_status m:null="true"/> +<d:tool_parm_name>40</d:tool_parm_name> +<d:unit_type_id>UNKNOWN</d:unit_type_id> +<d:create_date m:type="Edm.DateTime">2014-06-23T09:44:08</d:create_date> +<d:update_date m:type="Edm.DateTime" m:null="true"/><d:ref_value_source m:null="true"/> +<d:create_user_id>OWAT</d:create_user_id><d:update_user_id m:null="true"/> +<d:City m:type="Edm.String">TestCity</d:City> +<d:ID m:type="Edm.String">40</d:ID> +<d:Name m:type="Edm.String">TestName</d:Name> +<d:ZipCode m:type="Edm.Int32">111111</d:ZipCode> +<d:tool_parm_id m:type="Edm.Int32">221</d:tool_parm_id> +</m:properties></content></entry> \ No newline at end of file diff --git a/modules/org.restlet.test/src/org/restlet/test/batch/xml/getCafeResponse.xml b/modules/org.restlet.test/src/org/restlet/test/batch/xml/getCafeResponse.xml new file mode 100644 index 0000000000..90d32c3d40 --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/batch/xml/getCafeResponse.xml @@ -0,0 +1,24 @@ + + +HTTP/1.1 200 OK +Content-Type: application/xml;charset=utf-8 +Location: http://localhost:8111/Cafe.svc/Cafes('40') +DataServiceVersion: 3.0 + +<?xml version='1.0' encoding='utf-8'?><entry xmlns="http://www.w3.org/2005/Atom" +xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xml:base="http://134.132.100.20:8080/dsdataserver/dsl.svc/"> +<id>http://localhost:8111/Cafe.svc/Cafes('40')</id> +<title type="text"/><updated>2014-05-27T09:17:16Z</updated><author><name/></author><link rel="edit" title="Cafes" href="Cafes('40')"/><link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Cafe" type="application/xml;type=feed" title="Cafe" href="Cafes('40')Cafe"/><category term="Cafes" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme"/> +<content type="application/xml"> +<m:properties><d:ref_value_status m:null="true"/> +<d:tool_parm_name>40</d:tool_parm_name> +<d:unit_type_id>UNKNOWN</d:unit_type_id> +<d:create_date m:type="Edm.DateTime">2014-06-23T09:44:08</d:create_date> +<d:update_date m:type="Edm.DateTime" m:null="true"/><d:ref_value_source m:null="true"/> +<d:create_user_id>OWAT</d:create_user_id><d:update_user_id m:null="true"/> +<d:City m:type="Edm.String">TestCity</d:City> +<d:ID m:type="Edm.String">40</d:ID> +<d:Name m:type="Edm.String">TestName</d:Name> +<d:ZipCode m:type="Edm.Int32">111111</d:ZipCode> +<d:tool_parm_id m:type="Edm.Int32">221</d:tool_parm_id> +</m:properties></content></entry> \ No newline at end of file diff --git a/modules/org.restlet.test/src/org/restlet/test/batch/xml/metadata.xml b/modules/org.restlet.test/src/org/restlet/test/batch/xml/metadata.xml new file mode 100644 index 0000000000..4726582e39 --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/batch/xml/metadata.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<edmx:Edmx Version="1.0" + xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx"> + <edmx:DataServices> + <Schema Namespace="org.restlet.test.batch.crud" + xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" + xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" + xmlns="http://schemas.microsoft.com/ado/2006/04/edm"> + <EntityType Name="Cafe"> + <Key> + <PropertyRef Name="ID" /> + </Key> + <Property Name="ID" Type="Edm.String" Nullable="false" /> + <Property Name="Name" Type="Edm.String" Nullable="true" /> + <Property Name="ZipCode" Type="Edm.Int32" Nullable="false" /> + <Property Name="City" Type="Edm.String" Nullable="true" /> + <Property Name="MediaType" Type="Edm.String" Nullable="false"/> + <Property Name="baseMultiPart" Type="Edm.String" Nullable="false"/> + </EntityType> + <EntityContainer Name="CafeServiceDataModel" + m:IsDefaultEntityContainer="true"> + <EntitySet Name="Cafes" EntityType="org.restlet.test.batch.crud.Cafe" /> + </EntityContainer> + </Schema> + </edmx:DataServices> +</edmx:Edmx> \ No newline at end of file diff --git a/modules/org.restlet.test/src/org/restlet/test/batch/xml/parentBatchResponse.xml b/modules/org.restlet.test/src/org/restlet/test/batch/xml/parentBatchResponse.xml new file mode 100644 index 0000000000..a5ab7e7473 --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/batch/xml/parentBatchResponse.xml @@ -0,0 +1,9 @@ +HTTP/1.1 202 Accepted +Server: Apache-Coyote/1.1 +DataServiceVersion: 3.0 +Content-Type: multipart/mixed;boundary=batchresponse_1f82a90b-43f1-4276-b20a-59da3437dfa8 +Transfer-Encoding: chunked +Date: Mon, 23 June 2014 09:44:08 GMT +Proxy-Connection: Keep-Alive +Connection: Keep-Alive + diff --git a/modules/org.restlet.test/src/org/restlet/test/batch/xml/updateCafeResponse.xml b/modules/org.restlet.test/src/org/restlet/test/batch/xml/updateCafeResponse.xml new file mode 100644 index 0000000000..c6d23e6f34 --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/batch/xml/updateCafeResponse.xml @@ -0,0 +1,27 @@ + + +HTTP/1.1 200 OK +Content-Type: application/atom+xml;charset=utf-8 +Location: http://localhost:8111/Cafe.svc/Cafes('40') +DataServiceVersion: 3.0 + +<?xml version='1.0' encoding='utf-8'?><entry xmlns="http://www.w3.org/2005/Atom" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" +xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xml:base="http://134.132.100.20:8080/dsdataserver/dsl.svc/"> +<id>http://localhost:8111/Cafe.svc/Cafes('40')</id> +<title type="text"/><updated>2014-05-27T09:17:16Z</updated><author><name/></author><link rel="edit" title="Cafes" href="Cafes('40')"/><link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Cafes" type="application/atom+xml;type=feed" title="Cafes" href="Cafes('40')/Cafes"/><category term="Cafes" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme"/> +<content type="application/xml"> +<m:properties> +<d:ref_value_status m:null="true"/> +<d:tool_parm_name>40</d:tool_parm_name> +<d:unit_type_id>UNKNOWN</d:unit_type_id> +<d:create_date m:type="Edm.DateTime">2014-06-23T09:44:08</d:create_date> +<d:update_date m:type="Edm.DateTime" m:null="true"/> +<d:Name>TestName-updated</d:Name> +<d:create_user_id>OWAT</d:create_user_id><d:update_user_id m:null="true"/> +<d:City m:type="Edm.String">TestCity</d:City> +<d:ID m:type="Edm.String">40</d:ID> +<d:Name m:type="Edm.String">TestName</d:Name> +<d:ZipCode m:type="Edm.Int32">111111</d:ZipCode> + +</m:properties> +</content></entry> \ No newline at end of file diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/ODataCafeCustoFeedsTestCase.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/ODataCafeCustoFeedsTestCase.java index 1f6cd950f1..d58feb340d 100644 --- a/modules/org.restlet.test/src/org/restlet/test/ext/odata/ODataCafeCustoFeedsTestCase.java +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/ODataCafeCustoFeedsTestCase.java @@ -34,11 +34,14 @@ package org.restlet.test.ext.odata; import java.util.Iterator; +import java.util.List; import org.restlet.Component; import org.restlet.data.Protocol; import org.restlet.ext.odata.Query; import org.restlet.test.RestletTestCase; +import org.restlet.test.ext.odata.cafe.Point; +import org.restlet.test.ext.odata.cafe.StructAny; import org.restlet.test.ext.odata.cafecustofeeds.Cafe; import org.restlet.test.ext.odata.cafecustofeeds.CafeCustoFeedsService; import org.restlet.test.ext.odata.cafecustofeeds.Contact; @@ -97,6 +100,9 @@ public void testQueryCafes() { assertEquals("Levallois-Perret", cafe.getCity()); assertEquals(92300, cafe.getZipCode()); + assertNotNull(cafe.getSpatial()); + assertComplextTypeParsing(cafe.getSpatial()); + assertTrue(iterator.hasNext()); cafe = iterator.next(); assertEquals("2", cafe.getId()); @@ -105,6 +111,55 @@ public void testQueryCafes() { assertEquals("Marly Le Roi", cafe.getCity()); assertEquals(78310, cafe.getZipCode()); } + + /** + * This checks that if an entity has a property of complex type. + * Also it checks for the collection properties of primitives as well as complex type. + * + * @param point + * complex entity to test. + */ + private void assertComplextTypeParsing(Point point) { + + assertEquals("LINESTRING", point.getGeo_type()); + assertEquals("GEONAME", point.getGeo_name()); + + assertNotNull(point.getX()); + assertTrue(point.getX().size()>0); + + List<java.lang.Double> listX = point.getX(); + + for (Iterator iterator = listX.iterator(); iterator.hasNext();) { + Double element = (Double) iterator.next(); + assertTrue(element instanceof Double); + assertNotNull(element); + } + + assertNotNull(point.getY()); + assertTrue(point.getY().size()>0); + + List<java.lang.Double> listY = point.getY(); + + for (Iterator iterator = listY.iterator(); iterator.hasNext();) { + Double element = (Double) iterator.next(); + assertTrue(element instanceof Double); + assertNotNull(element); + } + + assertNotNull(point.getProperties()); + assertTrue(point.getProperties().size()>0); + + List<StructAny> listComplexObject = point.getProperties(); + + for (Iterator iterator = listComplexObject.iterator(); iterator.hasNext();) { + StructAny structAny = (StructAny) iterator.next(); + assertEquals("md", structAny.getName()); + assertEquals("FLOAT", structAny.getType()); + assertEquals("meters", structAny.getUnit()); + assertEquals("depth measure", structAny.getUnitType()); + assertEquals("[0.0,2670.9678]", structAny.getValues()); + } + } /** * Tests the parsing of Feed element with expansion of the one to one diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/ODataCafeTestCase.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/ODataCafeTestCase.java index 43af095205..535fc5eeb0 100644 --- a/modules/org.restlet.test/src/org/restlet/test/ext/odata/ODataCafeTestCase.java +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/ODataCafeTestCase.java @@ -34,7 +34,9 @@ package org.restlet.test.ext.odata; import java.util.Iterator; +import java.util.List; +import org.hamcrest.core.IsInstanceOf; import org.restlet.Component; import org.restlet.data.Protocol; import org.restlet.ext.odata.Query; @@ -43,6 +45,10 @@ import org.restlet.test.ext.odata.cafe.CafeService; import org.restlet.test.ext.odata.cafe.Contact; import org.restlet.test.ext.odata.cafe.Item; +import org.restlet.test.ext.odata.cafe.Point; +import org.restlet.test.ext.odata.cafe.StructAny; + +import sun.security.jca.GetInstance.Instance; /** * Test case for OData service. @@ -92,6 +98,10 @@ public void testQueryCafes() { assertEquals("Cafe corp.", cafe.getCompanyName()); assertEquals("Levallois-Perret", cafe.getCity()); assertEquals(92300, cafe.getZipCode()); + + //test complex type and collection type (Including collection of simple type as well as complex type). + assertNotNull(cafe.getSpatial()); + assertComplextTypeParsing(cafe.getSpatial()); assertTrue(iterator.hasNext()); cafe = iterator.next(); @@ -103,6 +113,55 @@ public void testQueryCafes() { } /** + * This checks that if an entity has a property of complex type. + * Also it checks for the collection properties of primitives as well as complex type. + * + * @param point + * complex entity to test. + */ + private void assertComplextTypeParsing(Point point) { + + assertEquals("LINESTRING", point.getGeo_type()); + assertEquals("GEONAME", point.getGeo_name()); + + assertNotNull(point.getX()); + assertTrue(point.getX().size()>0); + + List<java.lang.Double> listX = point.getX(); + + for (Iterator iterator = listX.iterator(); iterator.hasNext();) { + Double element = (Double) iterator.next(); + assertTrue(element instanceof Double); + assertNotNull(element); + } + + assertNotNull(point.getY()); + assertTrue(point.getY().size()>0); + + List<java.lang.Double> listY = point.getY(); + + for (Iterator iterator = listY.iterator(); iterator.hasNext();) { + Double element = (Double) iterator.next(); + assertTrue(element instanceof Double); + assertNotNull(element); + } + + assertNotNull(point.getProperties()); + assertTrue(point.getProperties().size()>0); + + List<StructAny> listComplexObject = point.getProperties(); + + for (Iterator iterator = listComplexObject.iterator(); iterator.hasNext();) { + StructAny structAny = (StructAny) iterator.next(); + assertEquals("md", structAny.getName()); + assertEquals("FLOAT", structAny.getType()); + assertEquals("meters", structAny.getUnit()); + assertEquals("depth measure", structAny.getUnitType()); + assertEquals("[0.0,2670.9678]", structAny.getValues()); + } + } + + /** * Tests the parsing of Feed element with expansion of the one to one * association "Contact". */ diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/ODataTestSuite.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/ODataTestSuite.java index 6238f86d51..31523ca268 100644 --- a/modules/org.restlet.test/src/org/restlet/test/ext/odata/ODataTestSuite.java +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/ODataTestSuite.java @@ -33,7 +33,11 @@ package org.restlet.test.ext.odata; +import org.restlet.test.ext.odata.complexcrud.ODataCafeCrudTestCase; import org.restlet.test.ext.odata.deepexpand.ODataDeepExpandTestCase; +import org.restlet.test.ext.odata.function.ActionTestCase; +import org.restlet.test.ext.odata.function.FunctionTestCase; +import org.restlet.test.ext.odata.streamcrud.ODataCafeCrudStreamTestCase; import junit.framework.Test; import junit.framework.TestSuite; @@ -56,6 +60,11 @@ public static Test suite() { result.addTestSuite(ODataCafeTestCase.class); result.addTestSuite(ODataCafeCustoFeedsTestCase.class); result.addTestSuite(ODataDeepExpandTestCase.class); + result.addTestSuite(ODataCafeCrudTestCase.class); + result.addTestSuite(org.restlet.test.ext.odata.crud.ODataCafeCrudTestCase.class); + result.addTestSuite(ODataCafeCrudStreamTestCase.class); + result.addTestSuite(ActionTestCase.class); + result.addTestSuite(FunctionTestCase.class); return result; } diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/cafe/Cafe.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/cafe/Cafe.java index eca5e1f3af..45a50dd33a 100644 --- a/modules/org.restlet.test/src/org/restlet/test/ext/odata/cafe/Cafe.java +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/cafe/Cafe.java @@ -34,6 +34,7 @@ package org.restlet.test.ext.odata.cafe; +import java.util.ArrayList; import java.util.List; import org.restlet.test.ext.odata.cafe.Contact; import org.restlet.test.ext.odata.cafe.Item; @@ -46,13 +47,14 @@ */ public class Cafe { +private Point spatial; private String city; private String companyName; private String id; private String name; private int zipCode; private Contact contact; -private List<Item> items; /** +private List<Item> items = new ArrayList<Item>(); /** * Constructor without parameter. * */ @@ -205,5 +207,19 @@ public void setItems(List<Item> items) { this.items = items; } +/** + * @return the spatial + */ +public Point getSpatial() { + return spatial; +} + +/** + * @param spatial the spatial to set + */ +public void setSpatial(Point spatial) { + this.spatial = spatial; +} + } diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/cafe/Point.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/cafe/Point.java new file mode 100644 index 0000000000..c29c4682dd --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/cafe/Point.java @@ -0,0 +1,153 @@ +/** + * Copyright 2005-2013 Restlet S.A.S. + * + * The contents of this file are subject to the terms of one of the following + * open source licenses: Apache 2.0 or LGPL 3.0 or LGPL 2.1 or CDDL 1.0 or EPL + * 1.0 (the "Licenses"). You can select the license that you prefer but you may + * not use this file except in compliance with one of these Licenses. + * + * You can obtain a copy of the Apache 2.0 license at + * http://www.opensource.org/licenses/apache-2.0 + * + * You can obtain a copy of the LGPL 3.0 license at + * http://www.opensource.org/licenses/lgpl-3.0 + * + * You can obtain a copy of the LGPL 2.1 license at + * http://www.opensource.org/licenses/lgpl-2.1 + * + * You can obtain a copy of the CDDL 1.0 license at + * http://www.opensource.org/licenses/cddl1 + * + * You can obtain a copy of the EPL 1.0 license at + * http://www.opensource.org/licenses/eclipse-1.0 + * + * See the Licenses for the specific language governing permissions and + * limitations under the Licenses. + * + * Alternatively, you can obtain a royalty free commercial license with less + * limitations, transferable or non-transferable, directly at + * http://www.restlet.com/products/restlet-framework + * + * Restlet is a registered trademark of Restlet S.A.S. + */ + +package org.restlet.test.ext.odata.cafe; + +import java.util.ArrayList; +import java.util.List; + +/** +* Generated for the OData extension for the Restlet framework.<br> +* +* @see <a href="http://localhost:8111/Cafe.svc/$metadata">Metadata of the target WCF Data Services</a> +* +*/ +public class Point { + + private String geo_type; + private String geo_name; + private List<StructAny> properties = new ArrayList<StructAny>(); + private List<java.lang.Double> x = new ArrayList<java.lang.Double>(); + private List<java.lang.Double> y = new ArrayList<java.lang.Double>(); + + /** + * Constructor without parameter. + * + */ + public Point() { + super(); + } + + /** + * Returns the value of the "geo_type" attribute. + * + * @return The value of the "geo_type" attribute. + */ + public String getGeo_type() { + return geo_type; + } + + + + /** + * Returns the value of the "x" attribute. + * + * @return The value of the "x" attribute. + */ + public List<java.lang.Double> getX() { + return x; + } + + /** + * Returns the value of the "y" attribute. + * + * @return The value of the "y" attribute. + */ + public List<java.lang.Double> getY() { + return y; + } + + + + /** + * Sets the value of the "geo_type" attribute. + * + * @param geo_type + * The value of the "geo_type" attribute. + */ + public void setGeo_type(String geo_type) { + this.geo_type = geo_type; + } + + + + /** + * Sets the value of the "x" attribute. + * + * @param x + * The value of the "x" attribute. + */ + public void setX(List<java.lang.Double> x) { + this.x = x; + } + + /** + * Sets the value of the "y" attribute. + * + * @param y + * The value of the "y" attribute. + */ + public void setY(List<java.lang.Double> y) { + this.y = y; + } + + +/** + * @return the geo_name + */ +public String getGeo_name() { + return geo_name; +} + +/** + * @param geo_name the geo_name to set + */ +public void setGeo_name(String geo_name) { + this.geo_name = geo_name; +} + +/** + * @return the properties + */ +public List<StructAny> getProperties() { + return properties; +} + +/** + * @param properties the properties to set + */ +public void setProperties(List<StructAny> properties) { + this.properties = properties; +} + +} \ No newline at end of file diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/cafe/StructAny.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/cafe/StructAny.java new file mode 100644 index 0000000000..894b4f1f38 --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/cafe/StructAny.java @@ -0,0 +1,156 @@ +/** + * Copyright 2005-2013 Restlet S.A.S. + * + * The contents of this file are subject to the terms of one of the following + * open source licenses: Apache 2.0 or LGPL 3.0 or LGPL 2.1 or CDDL 1.0 or EPL + * 1.0 (the "Licenses"). You can select the license that you prefer but you may + * not use this file except in compliance with one of these Licenses. + * + * You can obtain a copy of the Apache 2.0 license at + * http://www.opensource.org/licenses/apache-2.0 + * + * You can obtain a copy of the LGPL 3.0 license at + * http://www.opensource.org/licenses/lgpl-3.0 + * + * You can obtain a copy of the LGPL 2.1 license at + * http://www.opensource.org/licenses/lgpl-2.1 + * + * You can obtain a copy of the CDDL 1.0 license at + * http://www.opensource.org/licenses/cddl1 + * + * You can obtain a copy of the EPL 1.0 license at + * http://www.opensource.org/licenses/eclipse-1.0 + * + * See the Licenses for the specific language governing permissions and + * limitations under the Licenses. + * + * Alternatively, you can obtain a royalty free commercial license with less + * limitations, transferable or non-transferable, directly at + * http://www.restlet.com/products/restlet-framework + * + * Restlet is a registered trademark of Restlet S.A.S. + */ + +package org.restlet.test.ext.odata.cafe; + + + +/** +* Generated for the OData extension for the Restlet framework.<br> +* +* @see <a href="http://localhost:8111/Cafe.svc/$metadata">Metadata of the target WCF Data Services</a> +* +*/ +public class StructAny { + + private String name; + private String type; + private String unit; + private String unitType; + private String values; + + /** + * Constructor without parameter. + * + */ + public StructAny() { + super(); + } + + /** + * Returns the value of the "name" attribute. + * + * @return The value of the "name" attribute. + */ + public String getName() { + return name; + } + + /** + * Returns the value of the "type" attribute. + * + * @return The value of the "type" attribute. + */ + public String getType() { + return type; + } + + /** + * Returns the value of the "unit" attribute. + * + * @return The value of the "unit" attribute. + */ + public String getUnit() { + return unit; + } + + /** + * Returns the value of the "unitType" attribute. + * + * @return The value of the "unitType" attribute. + */ + public String getUnitType() { + return unitType; + } + + /** + * Returns the value of the "values" attribute. + * + * @return The value of the "values" attribute. + */ + public String getValues() { + return values; + } + + + /** + * Sets the value of the "name" attribute. + * + * @param name + * The value of the "name" attribute. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Sets the value of the "type" attribute. + * + * @param type + * The value of the "type" attribute. + */ + public void setType(String type) { + this.type = type; + } + + /** + * Sets the value of the "unit" attribute. + * + * @param unit + * The value of the "unit" attribute. + */ + public void setUnit(String unit) { + this.unit = unit; + } + + /** + * Sets the value of the "unitType" attribute. + * + * @param unitType + * The value of the "unitType" attribute. + */ + public void setUnitType(String unitType) { + this.unitType = unitType; + } + + /** + * Sets the value of the "values" attribute. + * + * @param values + * The value of the "values" attribute. + */ + public void setValues(String values) { + this.values = values; + } + +} \ No newline at end of file diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/cafe/cafes.xml b/modules/org.restlet.test/src/org/restlet/test/ext/odata/cafe/cafes.xml index d9b633cd59..8760e32983 100644 --- a/modules/org.restlet.test/src/org/restlet/test/ext/odata/cafe/cafes.xml +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/cafe/cafes.xml @@ -27,6 +27,27 @@ <d:ZipCode m:type="Edm.Int32">92300</d:ZipCode> <d:City>Levallois-Perret</d:City> <d:CompanyName>Cafe corp.</d:CompanyName> + <d:spatial m:type="cafe.Point"> + <d:geo_type>LINESTRING</d:geo_type> + <d:geo_name>GEONAME</d:geo_name> + <d:x m:type="Collection(Edm.Double)"> + <d:element>7.29</d:element> + <d:element>7.29</d:element> + </d:x> + <d:y m:type="Collection(Edm.Double)"> + <d:element>65.32000000000001</d:element> + <d:element>65.32000000000001</d:element> + </d:y> + <d:properties m:type="Collection(cafe.StructAny)"> + <d:element> + <d:name>md</d:name> + <d:type>FLOAT</d:type> + <d:unit>meters</d:unit> + <d:unitType>depth measure</d:unitType> + <d:values>[0.0,2670.9678]</d:values> + </d:element> + </d:properties> + </d:spatial> </m:properties> </content> </entry> diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/cafe/metadata.xml b/modules/org.restlet.test/src/org/restlet/test/ext/odata/cafe/metadata.xml index fd3ddcb1d4..102f4e8e88 100644 --- a/modules/org.restlet.test/src/org/restlet/test/ext/odata/cafe/metadata.xml +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/cafe/metadata.xml @@ -60,6 +60,21 @@ <End Role="Cafe" EntitySet="Cafes" /> </AssociationSet> </EntityContainer> + <ComplexType Name="StructAny"> + <Property Name="name" Nullable="true" Type="Edm.String"/> + <Property Name="type" Nullable="true" Type="Edm.String"/> + <Property Name="unit" Nullable="true" Type="Edm.String"/> + <Property Name="unitType" Nullable="true" Type="Edm.String"/> + <Property Name="values" Nullable="true" Type="Edm.String"/> + </ComplexType> + <ComplexType Name="Point"> + <Property Name="geo_type" Nullable="true" Type="Edm.String"/> + <Property Name="geo_name" Nullable="true" Type="Edm.String"/> + <Property Name="x" Nullable="true" Type="Collection(Edm.Double)"/> + <Property Name="y" Nullable="true" Type="Collection(Edm.Double)"/> + <Property Name="z" Nullable="true" Type="Collection(Edm.Double)"/> + <Property Name="properties" Nullable="true" Type="Collection(cafe.StructAny)"/> + </ComplexType> </Schema> </edmx:DataServices> </edmx:Edmx> \ No newline at end of file diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/cafecustofeeds/Cafe.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/cafecustofeeds/Cafe.java index 1ed6b7bb9b..5c774b5121 100644 --- a/modules/org.restlet.test/src/org/restlet/test/ext/odata/cafecustofeeds/Cafe.java +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/cafecustofeeds/Cafe.java @@ -34,7 +34,10 @@ package org.restlet.test.ext.odata.cafecustofeeds; +import java.util.ArrayList; import java.util.List; + +import org.restlet.test.ext.odata.cafe.Point; import org.restlet.test.ext.odata.cafecustofeeds.Contact; import org.restlet.test.ext.odata.cafecustofeeds.Item; @@ -46,13 +49,14 @@ */ public class Cafe { +private Point spatial; private String city; private String companyName; private String id; private String name; private int zipCode; private Contact contact; -private List<Item> items; /** +private List<Item> items = new ArrayList<Item>(); /** * Constructor without parameter. * */ @@ -205,5 +209,19 @@ public void setItems(List<Item> items) { this.items = items; } +/** + * @return the spatial + */ +public Point getSpatial() { + return spatial; +} + +/** + * @param spatial the spatial to set + */ +public void setSpatial(Point spatial) { + this.spatial = spatial; +} + } diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/cafecustofeeds/Point.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/cafecustofeeds/Point.java new file mode 100644 index 0000000000..7d5c316c1e --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/cafecustofeeds/Point.java @@ -0,0 +1,153 @@ +/** + * Copyright 2005-2013 Restlet S.A.S. + * + * The contents of this file are subject to the terms of one of the following + * open source licenses: Apache 2.0 or LGPL 3.0 or LGPL 2.1 or CDDL 1.0 or EPL + * 1.0 (the "Licenses"). You can select the license that you prefer but you may + * not use this file except in compliance with one of these Licenses. + * + * You can obtain a copy of the Apache 2.0 license at + * http://www.opensource.org/licenses/apache-2.0 + * + * You can obtain a copy of the LGPL 3.0 license at + * http://www.opensource.org/licenses/lgpl-3.0 + * + * You can obtain a copy of the LGPL 2.1 license at + * http://www.opensource.org/licenses/lgpl-2.1 + * + * You can obtain a copy of the CDDL 1.0 license at + * http://www.opensource.org/licenses/cddl1 + * + * You can obtain a copy of the EPL 1.0 license at + * http://www.opensource.org/licenses/eclipse-1.0 + * + * See the Licenses for the specific language governing permissions and + * limitations under the Licenses. + * + * Alternatively, you can obtain a royalty free commercial license with less + * limitations, transferable or non-transferable, directly at + * http://www.restlet.com/products/restlet-framework + * + * Restlet is a registered trademark of Restlet S.A.S. + */ + +package org.restlet.test.ext.odata.cafecustofeeds; + +import java.util.ArrayList; +import java.util.List; + +/** +* Generated for the OData extension for the Restlet framework.<br> +* +* @see <a href="http://localhost:8111/CafeCustoFeeds.svc/$metadata">Metadata of the target WCF Data Services</a> +* +*/ +public class Point { + + private String geo_type; + private String geo_name; + private List<StructAny> properties = new ArrayList<StructAny>(); + private List<java.lang.Double> x = new ArrayList<java.lang.Double>(); + private List<java.lang.Double> y = new ArrayList<java.lang.Double>(); + + /** + * Constructor without parameter. + * + */ + public Point() { + super(); + } + + /** + * Returns the value of the "geo_type" attribute. + * + * @return The value of the "geo_type" attribute. + */ + public String getGeo_type() { + return geo_type; + } + + + + /** + * Returns the value of the "x" attribute. + * + * @return The value of the "x" attribute. + */ + public List<java.lang.Double> getX() { + return x; + } + + /** + * Returns the value of the "y" attribute. + * + * @return The value of the "y" attribute. + */ + public List<java.lang.Double> getY() { + return y; + } + + + + /** + * Sets the value of the "geo_type" attribute. + * + * @param geo_type + * The value of the "geo_type" attribute. + */ + public void setGeo_type(String geo_type) { + this.geo_type = geo_type; + } + + + + /** + * Sets the value of the "x" attribute. + * + * @param x + * The value of the "x" attribute. + */ + public void setX(List<java.lang.Double> x) { + this.x = x; + } + + /** + * Sets the value of the "y" attribute. + * + * @param y + * The value of the "y" attribute. + */ + public void setY(List<java.lang.Double> y) { + this.y = y; + } + + +/** + * @return the geo_name + */ +public String getGeo_name() { + return geo_name; +} + +/** + * @param geo_name the geo_name to set + */ +public void setGeo_name(String geo_name) { + this.geo_name = geo_name; +} + +/** + * @return the properties + */ +public List<StructAny> getProperties() { + return properties; +} + +/** + * @param properties the properties to set + */ +public void setProperties(List<StructAny> properties) { + this.properties = properties; +} + +} \ No newline at end of file diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/cafecustofeeds/StructAny.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/cafecustofeeds/StructAny.java new file mode 100644 index 0000000000..9923968026 --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/cafecustofeeds/StructAny.java @@ -0,0 +1,156 @@ +/** + * Copyright 2005-2013 Restlet S.A.S. + * + * The contents of this file are subject to the terms of one of the following + * open source licenses: Apache 2.0 or LGPL 3.0 or LGPL 2.1 or CDDL 1.0 or EPL + * 1.0 (the "Licenses"). You can select the license that you prefer but you may + * not use this file except in compliance with one of these Licenses. + * + * You can obtain a copy of the Apache 2.0 license at + * http://www.opensource.org/licenses/apache-2.0 + * + * You can obtain a copy of the LGPL 3.0 license at + * http://www.opensource.org/licenses/lgpl-3.0 + * + * You can obtain a copy of the LGPL 2.1 license at + * http://www.opensource.org/licenses/lgpl-2.1 + * + * You can obtain a copy of the CDDL 1.0 license at + * http://www.opensource.org/licenses/cddl1 + * + * You can obtain a copy of the EPL 1.0 license at + * http://www.opensource.org/licenses/eclipse-1.0 + * + * See the Licenses for the specific language governing permissions and + * limitations under the Licenses. + * + * Alternatively, you can obtain a royalty free commercial license with less + * limitations, transferable or non-transferable, directly at + * http://www.restlet.com/products/restlet-framework + * + * Restlet is a registered trademark of Restlet S.A.S. + */ + +package org.restlet.test.ext.odata.cafecustofeeds; + + + +/** +* Generated for the OData extension for the Restlet framework.<br> +* +* @see <a href="http://localhost:8111/CafeCustoFeeds.svc/$metadata">Metadata of the target WCF Data Services</a> +* +*/ +public class StructAny { + + private String name; + private String type; + private String unit; + private String unitType; + private String values; + + /** + * Constructor without parameter. + * + */ + public StructAny() { + super(); + } + + /** + * Returns the value of the "name" attribute. + * + * @return The value of the "name" attribute. + */ + public String getName() { + return name; + } + + /** + * Returns the value of the "type" attribute. + * + * @return The value of the "type" attribute. + */ + public String getType() { + return type; + } + + /** + * Returns the value of the "unit" attribute. + * + * @return The value of the "unit" attribute. + */ + public String getUnit() { + return unit; + } + + /** + * Returns the value of the "unitType" attribute. + * + * @return The value of the "unitType" attribute. + */ + public String getUnitType() { + return unitType; + } + + /** + * Returns the value of the "values" attribute. + * + * @return The value of the "values" attribute. + */ + public String getValues() { + return values; + } + + + /** + * Sets the value of the "name" attribute. + * + * @param name + * The value of the "name" attribute. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Sets the value of the "type" attribute. + * + * @param type + * The value of the "type" attribute. + */ + public void setType(String type) { + this.type = type; + } + + /** + * Sets the value of the "unit" attribute. + * + * @param unit + * The value of the "unit" attribute. + */ + public void setUnit(String unit) { + this.unit = unit; + } + + /** + * Sets the value of the "unitType" attribute. + * + * @param unitType + * The value of the "unitType" attribute. + */ + public void setUnitType(String unitType) { + this.unitType = unitType; + } + + /** + * Sets the value of the "values" attribute. + * + * @param values + * The value of the "values" attribute. + */ + public void setValues(String values) { + this.values = values; + } + +} \ No newline at end of file diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/cafecustofeeds/cafes.xml b/modules/org.restlet.test/src/org/restlet/test/ext/odata/cafecustofeeds/cafes.xml index 147cb3d95d..b5b9b91ffa 100644 --- a/modules/org.restlet.test/src/org/restlet/test/ext/odata/cafecustofeeds/cafes.xml +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/cafecustofeeds/cafes.xml @@ -26,6 +26,27 @@ <d:Name>Le Cafe Louis</d:Name> <d:ZipCode m:type="Edm.Int32">92300</d:ZipCode> <d:City>Levallois-Perret</d:City> + <d:spatial m:type="OW5000.Point"> + <d:geo_type>LINESTRING</d:geo_type> + <d:geo_name>GEONAME</d:geo_name> + <d:x m:type="Collection(Edm.Double)"> + <d:element>7.29</d:element> + <d:element>7.29</d:element> + </d:x> + <d:y m:type="Collection(Edm.Double)"> + <d:element>65.32000000000001</d:element> + <d:element>65.32000000000001</d:element> + </d:y> + <d:properties m:type="Collection(OW5000.StructAny)"> + <d:element> + <d:name>md</d:name> + <d:type>FLOAT</d:type> + <d:unit>meters</d:unit> + <d:unitType>depth measure</d:unitType> + <d:values>[0.0,2670.9678]</d:values> + </d:element> + </d:properties> + </d:spatial> </m:properties> </content> <cafe:Company> diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/cafecustofeeds/metadata.xml b/modules/org.restlet.test/src/org/restlet/test/ext/odata/cafecustofeeds/metadata.xml index a2415d1278..f9fb5ca635 100644 --- a/modules/org.restlet.test/src/org/restlet/test/ext/odata/cafecustofeeds/metadata.xml +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/cafecustofeeds/metadata.xml @@ -70,6 +70,21 @@ <End Role="Cafe" EntitySet="Cafes" /> </AssociationSet> </EntityContainer> + <ComplexType Name="StructAny"> + <Property Name="name" Nullable="true" Type="Edm.String"/> + <Property Name="type" Nullable="true" Type="Edm.String"/> + <Property Name="unit" Nullable="true" Type="Edm.String"/> + <Property Name="unitType" Nullable="true" Type="Edm.String"/> + <Property Name="values" Nullable="true" Type="Edm.String"/> + </ComplexType> + <ComplexType Name="Point"> + <Property Name="geo_type" Nullable="true" Type="Edm.String"/> + <Property Name="geo_name" Nullable="true" Type="Edm.String"/> + <Property Name="x" Nullable="true" Type="Collection(Edm.Double)"/> + <Property Name="y" Nullable="true" Type="Collection(Edm.Double)"/> + <Property Name="z" Nullable="true" Type="Collection(Edm.Double)"/> + <Property Name="properties" Nullable="true" Type="Collection(OW5000.StructAny)"/> + </ComplexType> </Schema> </edmx:DataServices> </edmx:Edmx> \ No newline at end of file diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/complexcrud/Cafe.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/complexcrud/Cafe.java new file mode 100644 index 0000000000..f49496db75 --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/complexcrud/Cafe.java @@ -0,0 +1,158 @@ +package org.restlet.test.ext.odata.complexcrud; + + +import java.util.ArrayList; +import java.util.List; + +import org.restlet.test.ext.odata.cafe.Item; + +/** +* Generated by the generator tool for the WCF Data Services extension for the Restlet framework.<br> +* +* @see <a href="http://localhost:8111/Cafe.svc/$metadata">Metadata of the target WCF Data Services</a> +* +*/ +public class Cafe { + +private Point spatial; +private String city; + +private String id; +private String name; +private int zipCode; + +private List<Item> items = new ArrayList<Item>(); /** + * Constructor without parameter. + * + */ + public Cafe() { + super(); + } + + /** + * Constructor. + * + * @param id + * The identifiant value of the entity. + */ + public Cafe(String id) { + this(); + this.id = id; + } + + /** + * Returns the value of the city attribute. + * + * @return The value of the city attribute. + */ + public String getCity() { + return city; + } + + + /** + * Returns the value of the id attribute. + * + * @return The value of the id attribute. + */ + public String getId() { + return id; + } + + /** + * Returns the value of the name attribute. + * + * @return The value of the name attribute. + */ + public String getName() { + return name; + } + + /** + * Returns the value of the zipCode attribute. + * + * @return The value of the zipCode attribute. + */ + public int getZipCode() { + return zipCode; + } + + + /** + * Returns the value of the items attribute. + * + * @return The value of the items attribute. + */ + public List<Item> getItems() { + return items; + } + + + /** + * Sets the value of the city attribute. + * + * @param City + * The value of the city attribute. + */ + public void setCity(String city) { + this.city = city; + } + + + /** + * Sets the value of the id attribute. + * + * @param ID + * The value of the id attribute. + */ + public void setId(String id) { + this.id = id; + } + + /** + * Sets the value of the name attribute. + * + * @param Name + * The value of the name attribute. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Sets the value of the zipCode attribute. + * + * @param ZipCode + * The value of the zipCode attribute. + */ + public void setZipCode(int zipCode) { + this.zipCode = zipCode; + } + + + /** + * Sets the value of the items attribute. + * + * @param items + * The value of the items attribute. + */ + public void setItems(List<Item> items) { + this.items = items; + } + +/** + * @return the spatial + */ +public Point getSpatial() { + return spatial; +} + +/** + * @param spatial the spatial to set + */ +public void setSpatial(Point spatial) { + this.spatial = spatial; +} + + +} diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/complexcrud/CafeCrudApplication.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/complexcrud/CafeCrudApplication.java new file mode 100644 index 0000000000..af7c45bc0f --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/complexcrud/CafeCrudApplication.java @@ -0,0 +1,87 @@ +package org.restlet.test.ext.odata.complexcrud; + +import java.io.IOException; + +import org.restlet.Application; +import org.restlet.Context; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.Restlet; +import org.restlet.data.CharacterSet; +import org.restlet.data.Form; +import org.restlet.data.LocalReference; +import org.restlet.data.Method; +import org.restlet.data.Protocol; +import org.restlet.data.Status; +import org.restlet.routing.Router; + +/** + * Sample application that simulates cud operation for complex entities and + * collection. + */ +public class CafeCrudApplication extends Application { + private static class MyClapRestlet extends Restlet { + String file; + + @SuppressWarnings("unused") + boolean updatable; + + public MyClapRestlet(Context context, String file, boolean updatable) { + super(context); + this.file = file; + this.updatable = updatable; + } + + @SuppressWarnings("unused") + @Override + public void handle(Request request, Response response) { + + if (Method.GET.equals(request.getMethod())) { + Form form = request.getResourceRef().getQueryAsForm(); + String uri = "/" + + this.getClass().getPackage().getName() + .replace(".", "/") + "/" + file; + + Response r = getContext().getClientDispatcher().handle( + new Request(Method.GET, LocalReference + .createClapReference(LocalReference.CLAP_CLASS, + uri + ".xml"))); + response.setEntity(r.getEntity()); + response.setStatus(r.getStatus()); + + } else if (Method.POST.equals(request.getMethod()) || Method.PUT.equals(request.getMethod())) { + String rep=null; + try { + rep = request.getEntity().getText(); + } catch (IOException e) { + response.setStatus(Status.CLIENT_ERROR_BAD_REQUEST); + } + if(null != rep && !rep.isEmpty()){ + response.setStatus(Status.SUCCESS_OK); + } + + } else if (Method.DELETE.equals(request.getMethod())) { + response.setStatus(Status.SUCCESS_NO_CONTENT); + + } + + } + } + + + @Override + public Restlet createInboundRoot() { + getMetadataService().setDefaultCharacterSet(CharacterSet.ISO_8859_1); + getConnectorService().getClientProtocols().add(Protocol.CLAP); + Router router = new Router(getContext()); + + router.attach("/$metadata", new MyClapRestlet(getContext(), "metadata", + true)); + router.attach("/Cafes", new MyClapRestlet(getContext(), "cafes", true)); + router.attach("/Cafes('30')", new MyClapRestlet(getContext(), + "cafesUpdated", true)); + + return router; + } + +} diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/complexcrud/CafeService.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/complexcrud/CafeService.java new file mode 100644 index 0000000000..705b72739b --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/complexcrud/CafeService.java @@ -0,0 +1,44 @@ +package org.restlet.test.ext.odata.complexcrud; + +import org.restlet.ext.odata.Query; +import org.restlet.ext.odata.Service; + +/** +* Generated by the generator tool for the WCF Data Services extension for the Restlet framework.<br> +* +* @see <a href="http://localhost:8111/Cafe.svc/$metadata">Metadata of the target WCF Data Services</a> +* +*/ +public class CafeService extends Service { + + /** + * Constructor. + * + */ + public CafeService() { + super("http://localhost:8111/Cafe.svc"); + } + + /** + * Adds a new entity to the service. + * + * @param entity + * The entity to add to the service. + * @throws Exception + */ + public void addEntity(Cafe entity) throws Exception { + addEntity("/Cafes", entity); + } + + /** + * Creates a query for cafe entities hosted by this service. + * + * @param subpath + * The path to this entity relatively to the service URI. + * @return A query object. + */ + public Query<Cafe> createCafeQuery(String subpath) { + return createQuery(subpath, Cafe.class); + } + +} diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/complexcrud/ODataCafeCrudTestCase.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/complexcrud/ODataCafeCrudTestCase.java new file mode 100644 index 0000000000..e13c133ea0 --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/complexcrud/ODataCafeCrudTestCase.java @@ -0,0 +1,142 @@ +package org.restlet.test.ext.odata.complexcrud; + +import java.util.ArrayList; +import java.util.List; + +import junit.framework.Assert; + +import org.restlet.Component; +import org.restlet.Context; +import org.restlet.Response; +import org.restlet.data.Protocol; +import org.restlet.data.Status; +import org.restlet.ext.odata.Query; +import org.restlet.test.RestletTestCase; + +/** + * Test case for OData service for CUD operation on complex entities and + * collection. + * + */ +public class ODataCafeCrudTestCase extends RestletTestCase { + + /** Inner component. */ + private Component component = new Component(); + + /** OData service used for all tests. */ + @SuppressWarnings("unused") + private CafeService service; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + component.getServers().add(Protocol.HTTP, 8111); + component.getClients().add(Protocol.CLAP); + component.getDefaultHost().attach("/Cafe.svc", + new CafeCrudApplication()); + component.start(); + + service = new CafeService(); + } + + @Override + protected void tearDown() throws Exception { + component.stop(); + component = null; + super.tearDown(); + } + + /** + * Test method for crud operation on complex entities and collection. + */ + public void testCrudComplexEntity() { + CafeService service = new CafeService(); + + // create. + Cafe cafe = buildCafeEntity(); + + try { + service.addEntity(cafe); + } catch (Exception e) { + Context.getCurrentLogger().warning( + "Cannot add entity due to: " + e.getMessage()); + Assert.fail(); + } + + Query<Cafe> query = service.createCafeQuery("/Cafes"); + Cafe cafe1 = query.iterator().next(); + assertEquals("TestName", cafe1.getName()); + assertEquals("30", cafe1.getId()); + assertEquals(111111, cafe1.getZipCode()); + assertNotNull(cafe1.getSpatial()); + Response latestResponse = query.getService().getLatestResponse(); + assertEquals(Status.SUCCESS_OK, latestResponse.getStatus()); + + // // Update. + cafe1.setName("TestName-update"); + + try { + service.updateEntity(cafe1); + } catch (Exception e) { + Context.getCurrentLogger().warning( + "Cannot update entity due to: " + e.getMessage()); + Assert.fail(); + } + + Query<Cafe> query3 = service.createCafeQuery("/Cafes('30')"); + + Cafe cafe2 = query3.iterator().next(); + assertEquals("TestName-updated", cafe2.getName()); + assertNotNull(cafe1.getSpatial()); + latestResponse = query3.getService().getLatestResponse(); + assertEquals(Status.SUCCESS_OK, latestResponse.getStatus()); + + // Delete + try { + service.deleteEntity(cafe2); + } catch (Exception e) { + Context.getCurrentLogger().warning( + "Cannot delete entity due to: " + e.getMessage()); + Assert.fail(); + } + latestResponse = query3.getService().getLatestResponse(); + assertEquals(Status.SUCCESS_NO_CONTENT, latestResponse.getStatus()); + } + + private Cafe buildCafeEntity() { + Cafe cafe = new Cafe(); + cafe.setId("30"); + cafe.setName("TestName"); + cafe.setCity("TestCity"); + cafe.setZipCode(111111); + + Point point = new Point(); + point.setGeo_name("GEONAME"); + point.setGeo_type("LINESTRING"); + + StructAny structAny = new StructAny(); + structAny.setName("md"); + structAny.setType("FLOAT"); + structAny.setUnit("meters"); + structAny.setUnitType("depth measure"); + structAny.setValues("[0.0,2670.9678]"); + + List<StructAny> properties = new ArrayList<StructAny>(); + properties.add(structAny); + + List<java.lang.Double> x = new ArrayList<java.lang.Double>(); + x.add(7.29d); + x.add(7.29d); + List<java.lang.Double> y = new ArrayList<java.lang.Double>(); + y.add(65.32000000000001d); + y.add(65.32000000000001d); + + point.setProperties(properties); + point.setX(x); + point.setY(y); + cafe.setSpatial(point); + return cafe; + } + +} diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/complexcrud/Point.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/complexcrud/Point.java new file mode 100644 index 0000000000..ab85b873b5 --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/complexcrud/Point.java @@ -0,0 +1,120 @@ +package org.restlet.test.ext.odata.complexcrud; + +import java.util.ArrayList; +import java.util.List; + +/** +* Generated for the OData extension for the Restlet framework.<br> +* +* @see <a href="http://localhost:8111/Cafe.svc/$metadata">Metadata of the target WCF Data Services</a> +* +*/ +public class Point { + + private String geo_type; + private String geo_name; + private List<StructAny> properties = new ArrayList<StructAny>(); + private List<java.lang.Double> x = new ArrayList<java.lang.Double>(); + private List<java.lang.Double> y = new ArrayList<java.lang.Double>(); + + /** + * Constructor without parameter. + * + */ + public Point() { + super(); + } + + /** + * Returns the value of the "geo_type" attribute. + * + * @return The value of the "geo_type" attribute. + */ + public String getGeo_type() { + return geo_type; + } + + + + /** + * Returns the value of the "x" attribute. + * + * @return The value of the "x" attribute. + */ + public List<java.lang.Double> getX() { + return x; + } + + /** + * Returns the value of the "y" attribute. + * + * @return The value of the "y" attribute. + */ + public List<java.lang.Double> getY() { + return y; + } + + + + /** + * Sets the value of the "geo_type" attribute. + * + * @param geo_type + * The value of the "geo_type" attribute. + */ + public void setGeo_type(String geo_type) { + this.geo_type = geo_type; + } + + + + /** + * Sets the value of the "x" attribute. + * + * @param x + * The value of the "x" attribute. + */ + public void setX(List<java.lang.Double> x) { + this.x = x; + } + + /** + * Sets the value of the "y" attribute. + * + * @param y + * The value of the "y" attribute. + */ + public void setY(List<java.lang.Double> y) { + this.y = y; + } + + +/** + * @return the geo_name + */ +public String getGeo_name() { + return geo_name; +} + +/** + * @param geo_name the geo_name to set + */ +public void setGeo_name(String geo_name) { + this.geo_name = geo_name; +} + +/** + * @return the properties + */ +public List<StructAny> getProperties() { + return properties; +} + +/** + * @param properties the properties to set + */ +public void setProperties(List<StructAny> properties) { + this.properties = properties; +} + +} \ No newline at end of file diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/complexcrud/StructAny.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/complexcrud/StructAny.java new file mode 100644 index 0000000000..495e5c22c4 --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/complexcrud/StructAny.java @@ -0,0 +1,124 @@ + +package org.restlet.test.ext.odata.complexcrud; + + + +/** +* Generated for the OData extension for the Restlet framework.<br> +* +* @see <a href="http://localhost:8111/Cafe.svc/$metadata">Metadata of the target WCF Data Services</a> +* +*/ +public class StructAny { + + private String name; + private String type; + private String unit; + private String unitType; + private String values; + + /** + * Constructor without parameter. + * + */ + public StructAny() { + super(); + } + + /** + * Returns the value of the "name" attribute. + * + * @return The value of the "name" attribute. + */ + public String getName() { + return name; + } + + /** + * Returns the value of the "type" attribute. + * + * @return The value of the "type" attribute. + */ + public String getType() { + return type; + } + + /** + * Returns the value of the "unit" attribute. + * + * @return The value of the "unit" attribute. + */ + public String getUnit() { + return unit; + } + + /** + * Returns the value of the "unitType" attribute. + * + * @return The value of the "unitType" attribute. + */ + public String getUnitType() { + return unitType; + } + + /** + * Returns the value of the "values" attribute. + * + * @return The value of the "values" attribute. + */ + public String getValues() { + return values; + } + + + /** + * Sets the value of the "name" attribute. + * + * @param name + * The value of the "name" attribute. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Sets the value of the "type" attribute. + * + * @param type + * The value of the "type" attribute. + */ + public void setType(String type) { + this.type = type; + } + + /** + * Sets the value of the "unit" attribute. + * + * @param unit + * The value of the "unit" attribute. + */ + public void setUnit(String unit) { + this.unit = unit; + } + + /** + * Sets the value of the "unitType" attribute. + * + * @param unitType + * The value of the "unitType" attribute. + */ + public void setUnitType(String unitType) { + this.unitType = unitType; + } + + /** + * Sets the value of the "values" attribute. + * + * @param values + * The value of the "values" attribute. + */ + public void setValues(String values) { + this.values = values; + } + +} \ No newline at end of file diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/complexcrud/cafes.xml b/modules/org.restlet.test/src/org/restlet/test/ext/odata/complexcrud/cafes.xml new file mode 100644 index 0000000000..4724c0e3d2 --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/complexcrud/cafes.xml @@ -0,0 +1,51 @@ +<?xml version="1.0" encoding="iso-8859-1" standalone="yes"?> +<feed xml:base="http://localhost:8111/Cafe.svc/" + xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" + xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" + xmlns="http://www.w3.org/2005/Atom"> + <title type="text">Cafes + http://localhost:8111/Cafe.svc/Cafes + 2010-02-17T11:28:13Z + + + http://localhost:8111/Cafe.svc/Cafes('30') + + 2010-02-17T11:28:13Z + + + + + + + + 30 + TestName + 111111 + TestCity + + + LINESTRING + GEONAME + + + md + FLOAT + meters + depth measure + [0.0,2670.9678] + + + + 7.29 + 7.29 + + + 65.32000000000001 + 65.32000000000001 + + + + + +
\ No newline at end of file diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/complexcrud/cafesUpdated.xml b/modules/org.restlet.test/src/org/restlet/test/ext/odata/complexcrud/cafesUpdated.xml new file mode 100644 index 0000000000..89aecc6c8d --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/complexcrud/cafesUpdated.xml @@ -0,0 +1,51 @@ + + + Cafes + http://localhost:8111/Cafe.svc/Cafes + 2010-02-17T11:28:13Z + + + http://localhost:8111/Cafe.svc/Cafes('30') + + 2010-02-17T11:28:13Z + + + + + + + + 30 + TestName-updated + 111111 + TestCity + + + LINESTRING + GEONAME + + + md + FLOAT + meters + depth measure + [0.0,2670.9678] + + + + 7.29 + 7.29 + + + 65.32000000000001 + 65.32000000000001 + + + + + + \ No newline at end of file diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/complexcrud/metadata.xml b/modules/org.restlet.test/src/org/restlet/test/ext/odata/complexcrud/metadata.xml new file mode 100644 index 0000000000..b66f86e1f6 --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/complexcrud/metadata.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/crud/Cafe.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/crud/Cafe.java new file mode 100644 index 0000000000..28bb59e32e --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/crud/Cafe.java @@ -0,0 +1,120 @@ +package org.restlet.test.ext.odata.crud; + + + +/** +* Generated by the generator tool for the WCF Data Services extension for the Restlet framework.
+* +* @see Metadata of the target WCF Data Services +* +*/ +public class Cafe { + + +private String city; + +private String id; +private String name; +private int zipCode; + +/** + * Constructor without parameter. + * + */ + public Cafe() { + super(); + } + + /** + * Constructor. + * + * @param id + * The identifiant value of the entity. + */ + public Cafe(String id) { + this(); + this.id = id; + } + + /** + * Returns the value of the city attribute. + * + * @return The value of the city attribute. + */ + public String getCity() { + return city; + } + + + /** + * Returns the value of the id attribute. + * + * @return The value of the id attribute. + */ + public String getId() { + return id; + } + + /** + * Returns the value of the name attribute. + * + * @return The value of the name attribute. + */ + public String getName() { + return name; + } + + /** + * Returns the value of the zipCode attribute. + * + * @return The value of the zipCode attribute. + */ + public int getZipCode() { + return zipCode; + } + + + + + /** + * Sets the value of the city attribute. + * + * @param City + * The value of the city attribute. + */ + public void setCity(String city) { + this.city = city; + } + + + + /** + * Sets the value of the id attribute. + * + * @param ID + * The value of the id attribute. + */ + public void setId(String id) { + this.id = id; + } + + /** + * Sets the value of the name attribute. + * + * @param Name + * The value of the name attribute. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Sets the value of the zipCode attribute. + * + * @param ZipCode + * The value of the zipCode attribute. + */ + public void setZipCode(int zipCode) { + this.zipCode = zipCode; + } +} diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/crud/CafeCrudApplication.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/crud/CafeCrudApplication.java new file mode 100644 index 0000000000..2d85bfeedc --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/crud/CafeCrudApplication.java @@ -0,0 +1,84 @@ + +package org.restlet.test.ext.odata.crud; + +import java.io.IOException; + +import org.restlet.Application; +import org.restlet.Context; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.Restlet; +import org.restlet.data.CharacterSet; +import org.restlet.data.LocalReference; +import org.restlet.data.Method; +import org.restlet.data.Protocol; +import org.restlet.data.Status; +import org.restlet.routing.Router; + +/** + * Sample application that simulates the CUD operation on entities. + * + */ +@SuppressWarnings("unused") +public class CafeCrudApplication extends Application { + + private static class MyClapRestlet extends Restlet { + String file; + + boolean updatable; + + public MyClapRestlet(Context context, String file, boolean updatable) { + super(context); + this.file = file; + this.updatable = updatable; + } + + @Override + public void handle(Request request, Response response) { + if (Method.GET.equals(request.getMethod())) { + String uri = "/" + + this.getClass().getPackage().getName() + .replace(".", "/") + "/" + file; + + Response r = getContext().getClientDispatcher().handle( + new Request(Method.GET, LocalReference + .createClapReference(LocalReference.CLAP_CLASS, + uri + ".xml"))); + response.setEntity(r.getEntity()); + response.setStatus(r.getStatus()); + + } else if (Method.POST.equals(request.getMethod()) || Method.PUT.equals(request.getMethod())) { + String rep=null; + try { + rep = request.getEntity().getText(); + } catch (IOException e) { + response.setStatus(Status.CLIENT_ERROR_BAD_REQUEST); + } + if(null != rep && !rep.isEmpty()){ + response.setStatus(Status.SUCCESS_OK); + } + + } else if (Method.DELETE.equals(request.getMethod())) { + response.setStatus(Status.SUCCESS_NO_CONTENT); + + } + + } + } + + @Override + public Restlet createInboundRoot() { + getMetadataService().setDefaultCharacterSet(CharacterSet.ISO_8859_1); + getConnectorService().getClientProtocols().add(Protocol.CLAP); + Router router = new Router(getContext()); + + router.attach("/$metadata", new MyClapRestlet(getContext(), "metadata", + true)); + router.attach("/Cafes", new MyClapRestlet(getContext(), "cafes", true)); + router.attach("/Cafes('30')", new MyClapRestlet(getContext(), + "cafesUpdated", true)); + + return router; + } + +} diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/crud/CafeService.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/crud/CafeService.java new file mode 100644 index 0000000000..27228381d0 --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/crud/CafeService.java @@ -0,0 +1,77 @@ +/** + * Copyright 2005-2013 Restlet S.A.S. + * + * The contents of this file are subject to the terms of one of the following + * open source licenses: Apache 2.0 or LGPL 3.0 or LGPL 2.1 or CDDL 1.0 or EPL + * 1.0 (the "Licenses"). You can select the license that you prefer but you may + * not use this file except in compliance with one of these Licenses. + * + * You can obtain a copy of the Apache 2.0 license at + * http://www.opensource.org/licenses/apache-2.0 + * + * You can obtain a copy of the LGPL 3.0 license at + * http://www.opensource.org/licenses/lgpl-3.0 + * + * You can obtain a copy of the LGPL 2.1 license at + * http://www.opensource.org/licenses/lgpl-2.1 + * + * You can obtain a copy of the CDDL 1.0 license at + * http://www.opensource.org/licenses/cddl1 + * + * You can obtain a copy of the EPL 1.0 license at + * http://www.opensource.org/licenses/eclipse-1.0 + * + * See the Licenses for the specific language governing permissions and + * limitations under the Licenses. + * + * Alternatively, you can obtain a royalty free commercial license with less + * limitations, transferable or non-transferable, directly at + * http://www.restlet.com/products/restlet-framework + * + * Restlet is a registered trademark of Restlet S.A.S. + */ + +package org.restlet.test.ext.odata.crud; + +import org.restlet.ext.odata.Query; +import org.restlet.ext.odata.Service; + +/** +* Generated by the generator tool for the WCF Data Services extension for the Restlet framework.
+* +* @see Metadata of the target WCF Data Services +* +*/ +public class CafeService extends Service { + + /** + * Constructor. + * + */ + public CafeService() { + super("http://localhost:8111/Cafe.svc"); + } + + /** + * Adds a new entity to the service. + * + * @param entity + * The entity to add to the service. + * @throws Exception + */ + public void addEntity(Cafe entity) throws Exception { + addEntity("/Cafes", entity); + } + + /** + * Creates a query for cafe entities hosted by this service. + * + * @param subpath + * The path to this entity relatively to the service URI. + * @return A query object. + */ + public Query createCafeQuery(String subpath) { + return createQuery(subpath, Cafe.class); + } + +} diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/crud/ODataCafeCrudTestCase.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/crud/ODataCafeCrudTestCase.java new file mode 100644 index 0000000000..aa4dbd0ddb --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/crud/ODataCafeCrudTestCase.java @@ -0,0 +1,102 @@ +package org.restlet.test.ext.odata.crud; + +import junit.framework.Assert; + +import org.restlet.Component; +import org.restlet.Context; +import org.restlet.Response; +import org.restlet.data.Protocol; +import org.restlet.data.Status; +import org.restlet.ext.odata.Query; +import org.restlet.test.RestletTestCase; + +/** + * Test case for OData service for CUD operation on entities. + * + */ +public class ODataCafeCrudTestCase extends RestletTestCase { + + /** Inner component. */ + private Component component = new Component(); + + /** OData service used for all tests. */ + @SuppressWarnings("unused") + private CafeService service; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + component.getServers().add(Protocol.HTTP, 8111); + component.getClients().add(Protocol.CLAP); + component.getDefaultHost().attach("/Cafe.svc", + new CafeCrudApplication()); + component.start(); + + service = new CafeService(); + } + + @Override + protected void tearDown() throws Exception { + component.stop(); + component = null; + super.tearDown(); + } + + /** + * Test method for crud operation on simple entities. + */ + public void testCrudSimpleEntity() { + CafeService service = new CafeService(); + + // create. + Cafe cafe = new Cafe(); + cafe.setId("30"); + cafe.setName("TestName"); + cafe.setCity("TestCity"); + cafe.setZipCode(111111); + try { + service.addEntity(cafe); + } catch (Exception e) { + Context.getCurrentLogger().warning( + "Cannot add entity due to: " + e.getMessage()); + Assert.fail(); + } + + Query query = service.createCafeQuery("/Cafes"); + Cafe cafe1 = query.iterator().next(); + assertEquals("TestName", cafe1.getName()); + assertEquals("30", cafe1.getId()); + assertEquals(111111, cafe1.getZipCode()); + Response latestResponse = query.getService().getLatestResponse(); + assertEquals(Status.SUCCESS_OK, latestResponse.getStatus()); + // // Update. + cafe1.setName("TestName-update"); + + try { + service.updateEntity(cafe1); + } catch (Exception e) { + Context.getCurrentLogger().warning( + "Cannot update entity due to: " + e.getMessage()); + Assert.fail(); + } + + Query query3 = service.createCafeQuery("/Cafes('30')"); + + Cafe cafe2 = query3.iterator().next(); + assertEquals("TestName-updated", cafe2.getName()); + latestResponse = query3.getService().getLatestResponse(); + assertEquals(Status.SUCCESS_OK, latestResponse.getStatus()); + // Delete + try { + service.deleteEntity(cafe2); + } catch (Exception e) { + Context.getCurrentLogger().warning( + "Cannot delete entity due to: " + e.getMessage()); + Assert.fail(); + } + latestResponse = query3.getService().getLatestResponse(); + assertEquals(Status.SUCCESS_NO_CONTENT, latestResponse.getStatus()); + } + +} diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/crud/cafes.xml b/modules/org.restlet.test/src/org/restlet/test/ext/odata/crud/cafes.xml new file mode 100644 index 0000000000..1098176fde --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/crud/cafes.xml @@ -0,0 +1,29 @@ + + + Cafes + http://localhost:8111/Cafe.svc/Cafes + 2010-02-17T11:28:13Z + + + http://localhost:8111/Cafe.svc/Cafes('30') + + 2010-02-17T11:28:13Z + + + + + + + + 30 + TestName + 111111 + TestCity + + + + \ No newline at end of file diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/crud/cafesUpdated.xml b/modules/org.restlet.test/src/org/restlet/test/ext/odata/crud/cafesUpdated.xml new file mode 100644 index 0000000000..95493e94db --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/crud/cafesUpdated.xml @@ -0,0 +1,29 @@ + + + Cafes + http://localhost:8111/Cafe.svc/Cafes + 2010-02-17T11:28:13Z + + + http://localhost:8111/Cafe.svc/Cafes('30') + + 2010-02-17T11:28:13Z + + + + + + + + 30 + TestName-updated + 111111 + TestCity + + + + \ No newline at end of file diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/crud/metadata.xml b/modules/org.restlet.test/src/org/restlet/test/ext/odata/crud/metadata.xml new file mode 100644 index 0000000000..8ec48f6592 --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/crud/metadata.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/ActivitySector.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/ActivitySector.java index e3f6c13da1..92965aab8d 100644 --- a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/ActivitySector.java +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/ActivitySector.java @@ -34,6 +34,7 @@ package org.restlet.test.ext.odata.deepexpand.model; +import java.util.ArrayList; import java.util.List; import org.restlet.test.ext.odata.deepexpand.model.ActivitySector; @@ -50,8 +51,8 @@ public class ActivitySector { private String code; private String description; private int id; - private List childActivitySectors; - private List companies; + private List childActivitySectors = new ArrayList(); + private List companies = new ArrayList(); private ActivitySector parentActivitySector; /** diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/AuthenticatedUser.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/AuthenticatedUser.java index e9ea681f57..70989b52a2 100644 --- a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/AuthenticatedUser.java +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/AuthenticatedUser.java @@ -34,6 +34,7 @@ package org.restlet.test.ext.odata.deepexpand.model; +import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -64,13 +65,13 @@ public class AuthenticatedUser { private String surname; private String userName; private Tracking tracking; - private List
addresses; + private List
addresses = new ArrayList
(); private CoOp defaultCoOp; private Department department; private Language preferredLanguage; - private List reports; - private List roles; - private List telephones; + private List reports = new ArrayList(); + private List roles = new ArrayList(); + private List telephones = new ArrayList(); /** * Constructor without parameter. diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Branch.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Branch.java index 3b712586ba..931c13ffb6 100644 --- a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Branch.java +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Branch.java @@ -34,6 +34,7 @@ package org.restlet.test.ext.odata.deepexpand.model; +import java.util.ArrayList; import java.util.List; import org.restlet.test.ext.odata.deepexpand.model.Company; @@ -56,10 +57,10 @@ public class Branch { private EmbeddableAddress address; private Tracking tracking; private Company company; - private List jobParts; - private List jobPostingParts; + private List jobParts = new ArrayList(); + private List jobPostingParts = new ArrayList(); private Multilingual name; - private List persons; + private List persons = new ArrayList(); /** * Constructor without parameter. diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Category.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Category.java index e582f21c0a..4326619640 100644 --- a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Category.java +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Category.java @@ -34,6 +34,7 @@ package org.restlet.test.ext.odata.deepexpand.model; +import java.util.ArrayList; import java.util.List; import org.restlet.test.ext.odata.deepexpand.model.Category; @@ -52,11 +53,11 @@ public class Category { private int id; private String path; private Tracking tracking; - private List childCategories; - private List companies; + private List childCategories = new ArrayList(); + private List companies = new ArrayList(); private Multilingual name; private Category parentCategory; - private List preferredByRegistrations; + private List preferredByRegistrations = new ArrayList(); /** * Constructor without parameter. diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/CoOp.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/CoOp.java index 30f65d0ba4..9bf0459950 100644 --- a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/CoOp.java +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/CoOp.java @@ -34,6 +34,7 @@ package org.restlet.test.ext.odata.deepexpand.model; +import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -80,20 +81,20 @@ public class CoOp { private boolean supportingInvitations; private Tracking tracking; private Professor academicDirector; - private List authenticatedUsers; - private List companies; - private List financialSources; - private List groups; + private List authenticatedUsers = new ArrayList(); + private List companies = new ArrayList(); + private List financialSources = new ArrayList(); + private List groups = new ArrayList(); private Professor institutionalDirector; - private List insuranceContracts; - private List jobPostings; + private List insuranceContracts = new ArrayList(); + private List jobPostings = new ArrayList(); private Lesson lesson; private Multilingual name; - private List registrations; - private List reports; - private List requirements; + private List registrations = new ArrayList(); + private List reports = new ArrayList(); + private List requirements = new ArrayList(); private Professor scientificDirector; - private List supervisingProfessors; + private List supervisingProfessors = new ArrayList(); /** * Constructor without parameter. diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Company.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Company.java index 59b82b2fe2..476fd6a1e8 100644 --- a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Company.java +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Company.java @@ -34,6 +34,7 @@ package org.restlet.test.ext.odata.deepexpand.model; +import java.util.ArrayList; import java.util.List; import org.restlet.test.ext.odata.deepexpand.model.ActivitySector; @@ -61,16 +62,16 @@ public class Company { private String webSite; private Tracking tracking; private ActivitySector activitySector; - private List branches; + private List branches = new ArrayList(); private Category category; private Branch centralBranch; private Multilingual comments; private CompanyPerson contactPerson; - private List coOps; - private List insuranceContracts; - private List jobPostings; + private List coOps = new ArrayList(); + private List insuranceContracts = new ArrayList(); + private List jobPostings = new ArrayList(); private Multilingual name; - private List persons; + private List persons = new ArrayList(); /** * Constructor without parameter. diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/CompanyPerson.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/CompanyPerson.java index e9e1afb2e0..9fb1b5799a 100644 --- a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/CompanyPerson.java +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/CompanyPerson.java @@ -34,6 +34,7 @@ package org.restlet.test.ext.odata.deepexpand.model; +import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -67,14 +68,14 @@ public class CompanyPerson { private String salutation; private String surname; private Tracking tracking; - private List
addresses; - private List branches; + private List
addresses = new ArrayList
(); + private List branches = new ArrayList(); private Company company; - private List managedJobParts; - private List managedJobPostingParts; - private List managedJobPostings; + private List managedJobParts = new ArrayList(); + private List managedJobPostingParts = new ArrayList(); + private List managedJobPostings = new ArrayList(); private Language preferredLanguage; - private List telephones; + private List telephones = new ArrayList(); /** * Constructor without parameter. diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Department.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Department.java index 9ec3387c1d..9e062efce9 100644 --- a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Department.java +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Department.java @@ -34,6 +34,7 @@ package org.restlet.test.ext.odata.deepexpand.model; +import java.util.ArrayList; import java.util.List; import org.restlet.test.ext.odata.deepexpand.model.AuthenticatedUser; @@ -53,8 +54,8 @@ public class Department { private int id; private Tracking tracking; private List authenticatedUsers; - private List divisions; - private List lessons; + private List divisions = new ArrayList(); + private List lessons = new ArrayList(); private Multilingual name; private University university; diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Division.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Division.java index 5c6c234144..615ea6e12d 100644 --- a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Division.java +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Division.java @@ -34,6 +34,7 @@ package org.restlet.test.ext.odata.deepexpand.model; +import java.util.ArrayList; import java.util.List; import org.restlet.test.ext.odata.deepexpand.model.Department; @@ -51,7 +52,7 @@ public class Division { private int id; private Tracking tracking; private Department department; - private List facultyUsers; + private List facultyUsers = new ArrayList(); private Multilingual name; /** diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/FacultyUser.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/FacultyUser.java index 892879f26b..2e2728a566 100644 --- a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/FacultyUser.java +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/FacultyUser.java @@ -34,6 +34,7 @@ package org.restlet.test.ext.odata.deepexpand.model; +import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -65,14 +66,14 @@ public class FacultyUser { private String surname; private String userName; private Tracking tracking; - private List
addresses; + private List
addresses = new ArrayList
(); private CoOp defaultCoOp; private Department department; private Division division; private Language preferredLanguage; private List reports; - private List roles; - private List telephones; + private List roles = new ArrayList(); + private List telephones = new ArrayList(); /** * Constructor without parameter. diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/FinancialSource.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/FinancialSource.java index 0cbe8837bc..b33d5dbabe 100644 --- a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/FinancialSource.java +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/FinancialSource.java @@ -34,6 +34,7 @@ package org.restlet.test.ext.odata.deepexpand.model; +import java.util.ArrayList; import java.util.List; import org.restlet.test.ext.odata.deepexpand.model.CoOp; @@ -54,10 +55,10 @@ public class FinancialSource { private int id; private String name; private Tracking tracking; - private List coOps; - private List jobPartSpecialPayables; - private List jobPostingPartSpecialPayables; - private List payments; + private List coOps = new ArrayList(); + private List jobPartSpecialPayables = new ArrayList(); + private List jobPostingPartSpecialPayables = new ArrayList(); + private List payments = new ArrayList(); /** * Constructor without parameter. diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Group.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Group.java index 1369abefc4..3fe54dd294 100644 --- a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Group.java +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Group.java @@ -34,6 +34,7 @@ package org.restlet.test.ext.odata.deepexpand.model; +import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -60,11 +61,11 @@ public class Group { private boolean passed; private Tracking tracking; private CoOp coOp; - private List invitations; + private List invitations = new ArrayList(); private Job job; - private List registrations; - private List reports; - private List supervisingProfessors; + private List registrations = new ArrayList(); + private List reports = new ArrayList(); + private List supervisingProfessors = new ArrayList(); /** * Constructor without parameter. diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/InsuranceContract.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/InsuranceContract.java index aafddcbdc5..4b26703a2f 100644 --- a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/InsuranceContract.java +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/InsuranceContract.java @@ -34,6 +34,7 @@ package org.restlet.test.ext.odata.deepexpand.model; +import java.util.ArrayList; import java.util.List; import org.restlet.test.ext.odata.deepexpand.model.Attachment; @@ -52,10 +53,10 @@ public class InsuranceContract { private int id; private String name; private Tracking tracking; - private List attachments; + private List attachments = new ArrayList(); private CoOp coop; private Company insuranceCompany; - private List registrations; + private List registrations = new ArrayList(); /** * Constructor without parameter. diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Invitation.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Invitation.java index 615ae2e549..8de1b38a04 100644 --- a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Invitation.java +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Invitation.java @@ -34,6 +34,7 @@ package org.restlet.test.ext.odata.deepexpand.model; +import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -53,7 +54,7 @@ public class Invitation { private int id; private Tracking tracking; private Group group; - private List recepients; + private List recepients = new ArrayList(); private Registration sender; private Multilingual text; diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Job.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Job.java index 9f82a65743..a3c33db7f9 100644 --- a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Job.java +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Job.java @@ -34,6 +34,7 @@ package org.restlet.test.ext.odata.deepexpand.model; +import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -57,7 +58,7 @@ public class Job { private String state; private Tracking tracking; private Group group; - private List jobParts; + private List jobParts = new ArrayList(); private JobPosting jobPosting; private Professor supervisingProfessor; diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/JobPart.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/JobPart.java index fb4af44fb1..f9432e3e8d 100644 --- a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/JobPart.java +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/JobPart.java @@ -34,6 +34,7 @@ package org.restlet.test.ext.odata.deepexpand.model; +import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -67,9 +68,9 @@ public class JobPart { private Location expeditionLocation; private Job job; private CompanyPerson managingCompanyPerson; - private List payments; - private List reports; - private List specialPayables; + private List payments = new ArrayList(); + private List reports = new ArrayList(); + private List specialPayables = new ArrayList(); /** * Constructor without parameter. diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/JobPosting.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/JobPosting.java index 8aa9812490..1be93a4bd6 100644 --- a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/JobPosting.java +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/JobPosting.java @@ -34,6 +34,7 @@ package org.restlet.test.ext.odata.deepexpand.model; +import java.util.ArrayList; import java.util.List; import org.restlet.test.ext.odata.deepexpand.model.CoOp; @@ -60,11 +61,11 @@ public class JobPosting { private Company company; private CoOp coOp; private Multilingual description; - private List jobPostingParts; - private List jobs; + private List jobPostingParts = new ArrayList(); + private List jobs = new ArrayList(); private CompanyPerson managingCompanyPerson; private Multilingual name; - private List preferredByRegistrations; + private List preferredByRegistrations = new ArrayList(); private Professor supervisingProfessor; /** diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/JobPostingPart.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/JobPostingPart.java index 754f7d208a..67657be043 100644 --- a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/JobPostingPart.java +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/JobPostingPart.java @@ -34,6 +34,7 @@ package org.restlet.test.ext.odata.deepexpand.model; +import java.util.ArrayList; import java.util.List; import org.restlet.test.ext.odata.deepexpand.model.Branch; @@ -63,7 +64,7 @@ public class JobPostingPart { private Location expeditionLocation; private JobPosting jobPosting; private CompanyPerson managingCompanyPerson; - private List specialPayables; + private List specialPayables = new ArrayList(); /** * Constructor without parameter. diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Language.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Language.java index b5b25db30a..d15a12d945 100644 --- a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Language.java +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Language.java @@ -34,6 +34,7 @@ package org.restlet.test.ext.odata.deepexpand.model; +import java.util.ArrayList; import java.util.List; import org.restlet.test.ext.odata.deepexpand.model.Person; @@ -50,7 +51,7 @@ public class Language { private int id; private String localeCode; private String name; - private List preferredByPersons; + private List preferredByPersons = new ArrayList(); /** * Constructor without parameter. diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Lesson.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Lesson.java index 0fd5c46644..bb046d4dd7 100644 --- a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Lesson.java +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Lesson.java @@ -34,6 +34,7 @@ package org.restlet.test.ext.odata.deepexpand.model; +import java.util.ArrayList; import java.util.List; import org.restlet.test.ext.odata.deepexpand.model.CoOp; @@ -50,7 +51,7 @@ public class Lesson { private int id; private Tracking tracking; - private List coOps; + private List coOps = new ArrayList(); private Department department; private Multilingual name; diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Location.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Location.java index 795e7ccc02..f00c9fd6a4 100644 --- a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Location.java +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Location.java @@ -34,6 +34,7 @@ package org.restlet.test.ext.odata.deepexpand.model; +import java.util.ArrayList; import java.util.List; import org.restlet.test.ext.odata.deepexpand.model.JobPart; @@ -55,12 +56,12 @@ public class Location { private String path; private String type; private Tracking tracking; - private List childLocations; - private List issuedIdStudents; - private List jobPartsInExpedition; - private List jobPostingPartsInExpedition; + private List childLocations = new ArrayList(); + private List issuedIdStudents = new ArrayList(); + private List jobPartsInExpedition = new ArrayList(); + private List jobPostingPartsInExpedition = new ArrayList(); private Location parentLocation; - private List preferredByRegistrations; + private List preferredByRegistrations = new ArrayList(); /** * Constructor without parameter. diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Multilingual.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Multilingual.java index 952037f1b4..d344581521 100644 --- a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Multilingual.java +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Multilingual.java @@ -34,6 +34,7 @@ package org.restlet.test.ext.odata.deepexpand.model; +import java.util.ArrayList; import java.util.List; import org.restlet.test.ext.odata.deepexpand.model.Literal; @@ -47,7 +48,7 @@ public class Multilingual { private int id; - private List literals; + private List literals = new ArrayList(); /** * Constructor without parameter. diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Nationality.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Nationality.java index db4336db63..399e0d95a5 100644 --- a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Nationality.java +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Nationality.java @@ -34,6 +34,7 @@ package org.restlet.test.ext.odata.deepexpand.model; +import java.util.ArrayList; import java.util.List; import org.restlet.test.ext.odata.deepexpand.model.Multilingual; @@ -50,7 +51,7 @@ public class Nationality { private String code; private int id; private Multilingual name; - private List students; + private List students = new ArrayList(); /** * Constructor without parameter. diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Permission.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Permission.java index 5afe96bcfe..e05514c6e4 100644 --- a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Permission.java +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Permission.java @@ -34,6 +34,7 @@ package org.restlet.test.ext.odata.deepexpand.model; +import java.util.ArrayList; import java.util.List; import org.restlet.test.ext.odata.deepexpand.model.EntityAccess; @@ -52,8 +53,8 @@ public class Permission { private String managerName; private String name; private Tracking tracking; - private List entityAccesses; - private List roles; + private List entityAccesses = new ArrayList(); + private List roles = new ArrayList(); /** * Constructor without parameter. diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Person.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Person.java index ff4e2f9fcb..ad65b1ab7f 100644 --- a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Person.java +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Person.java @@ -34,6 +34,7 @@ package org.restlet.test.ext.odata.deepexpand.model; +import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -59,9 +60,9 @@ public class Person { private String notes; private String surname; private Tracking tracking; - private List
addresses; - private Language preferredLanguage; - private List telephones; + private List
addresses = new ArrayList
(); + private Language preferredLanguage ; + private List telephones = new ArrayList(); /** * Constructor without parameter. diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Professor.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Professor.java index f340d89783..c076f69ae3 100644 --- a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Professor.java +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Professor.java @@ -34,6 +34,7 @@ package org.restlet.test.ext.odata.deepexpand.model; +import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -69,21 +70,21 @@ public class Professor { private String surname; private String userName; private Tracking tracking; - private List academicallyDirectedCoOps; - private List
addresses; + private List academicallyDirectedCoOps = new ArrayList(); + private List
addresses = new ArrayList
(); private CoOp defaultCoOp; private Department department; private Division division; - private List institutionallyDirectedCoOps; + private List institutionallyDirectedCoOps = new ArrayList(); private Language preferredLanguage; - private List reports; - private List roles; - private List scientificallyDirectedCoOps; - private List supervisedCoOps; - private List supervisedGroups; - private List supervisedJobPostings; - private List supervisedJobs; - private List telephones; + private List reports = new ArrayList(); + private List roles = new ArrayList(); + private List scientificallyDirectedCoOps = new ArrayList(); + private List supervisedCoOps = new ArrayList(); + private List supervisedGroups = new ArrayList(); + private List supervisedJobPostings = new ArrayList(); + private List supervisedJobs = new ArrayList(); + private List telephones = new ArrayList(); /** * Constructor without parameter. diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Registration.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Registration.java index 766c387a01..1330ec5efd 100644 --- a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Registration.java +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Registration.java @@ -34,6 +34,7 @@ package org.restlet.test.ext.odata.deepexpand.model; +import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -74,14 +75,14 @@ public class Registration { private CoOp coop; private Group group; private InsuranceContract insuranceContract; - private List meetsRequirements; - private List payments; - private List preferredCategories; - private List preferredJobPostings; - private List preferredLocations; - private List receivedInvitations; - private List reports; - private List sentInvitations; + private List meetsRequirements = new ArrayList(); + private List payments = new ArrayList(); + private List preferredCategories = new ArrayList(); + private List preferredJobPostings = new ArrayList(); + private List preferredLocations = new ArrayList(); + private List receivedInvitations = new ArrayList(); + private List reports = new ArrayList(); + private List sentInvitations = new ArrayList(); private Student student; /** diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Report.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Report.java index 6157c96bb7..f091306fe7 100644 --- a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Report.java +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Report.java @@ -34,6 +34,7 @@ package org.restlet.test.ext.odata.deepexpand.model; +import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -59,7 +60,7 @@ public class Report { private int id; private String title; private Tracking tracking; - private List attachments; + private List attachments = new ArrayList(); private CoOp coOp; private Group group; private JobPart jobPart; diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/ReportType.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/ReportType.java index cbe7b111fd..f6c1150eb2 100644 --- a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/ReportType.java +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/ReportType.java @@ -34,6 +34,7 @@ package org.restlet.test.ext.odata.deepexpand.model; +import java.util.ArrayList; import java.util.List; import org.restlet.test.ext.odata.deepexpand.model.Multilingual; @@ -57,8 +58,8 @@ public class ReportType { private Tracking tracking; private Multilingual comments; private Multilingual name; - private List reports; - private List roles; + private List reports = new ArrayList(); + private List roles = new ArrayList(); /** * Constructor without parameter. diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Requirement.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Requirement.java index 76e6aa695d..cfc13fa1f9 100644 --- a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Requirement.java +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Requirement.java @@ -34,6 +34,7 @@ package org.restlet.test.ext.odata.deepexpand.model; +import java.util.ArrayList; import java.util.List; import org.restlet.test.ext.odata.deepexpand.model.CoOp; @@ -52,7 +53,7 @@ public class Requirement { private String type; private Tracking tracking; private CoOp coOp; - private List fullfillingRegistrations; + private List fullfillingRegistrations = new ArrayList(); private Multilingual name; /** diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Role.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Role.java index 5b3a5ef7c5..236274e61e 100644 --- a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Role.java +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Role.java @@ -34,6 +34,7 @@ package org.restlet.test.ext.odata.deepexpand.model; +import java.util.ArrayList; import java.util.List; import org.restlet.test.ext.odata.deepexpand.model.AuthenticatedUser; @@ -52,9 +53,9 @@ public class Role { private int id; private String name; private Tracking tracking; - private List permissions; - private List reportType; - private List users; + private List permissions = new ArrayList(); + private List reportType = new ArrayList(); + private List users = new ArrayList(); /** * Constructor without parameter. diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Student.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Student.java index 4e1ef9523b..c755dbbee4 100644 --- a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Student.java +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/Student.java @@ -34,6 +34,7 @@ package org.restlet.test.ext.odata.deepexpand.model; +import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -87,16 +88,16 @@ public class Student { private String workExperience; private Tracking tracking; private Registration activeRegistration; - private List
addresses; + private List
addresses = new ArrayList
(); private CoOp defaultCoOp; private Department department; private Location issuerLocation; private Nationality nationality; private Language preferredLanguage; - private List registrations; - private List reports; - private List roles; - private List telephones; + private List registrations = new ArrayList(); + private List reports = new ArrayList(); + private List roles = new ArrayList(); + private List telephones = new ArrayList(); /** * Constructor without parameter. diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/University.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/University.java index cb18dad437..0d0fa6d5e8 100644 --- a/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/University.java +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/deepexpand/model/University.java @@ -34,6 +34,7 @@ package org.restlet.test.ext.odata.deepexpand.model; +import java.util.ArrayList; import java.util.List; import org.restlet.test.ext.odata.deepexpand.model.Department; @@ -50,7 +51,7 @@ public class University { private int id; private EmbeddableAddress address; private Tracking tracking; - private List departments; + private List departments = new ArrayList(); private Multilingual name; /** diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/function/ActionTestCase.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/function/ActionTestCase.java new file mode 100644 index 0000000000..471b90c0c0 --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/function/ActionTestCase.java @@ -0,0 +1,66 @@ +package org.restlet.test.ext.odata.function; + + +import java.util.ArrayList; +import java.util.List; + +import junit.framework.Assert; + +import org.restlet.Component; +import org.restlet.Context; +import org.restlet.data.Protocol; +import org.restlet.test.RestletTestCase; + +/** + * Test case for actions in Restlet. + */ +public class ActionTestCase extends RestletTestCase { + + /** Inner component. */ + private Component component = new Component(); + + /** OData service used for all tests. */ + private FunctionService service; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + component.getServers().add(Protocol.HTTP, 8111); + component.getClients().add(Protocol.CLAP); + component.getDefaultHost().attach("/Unit.svc", + new UnitApplication()); + component.start(); + + service = new FunctionService(); + } + + @Override + protected void tearDown() throws Exception { + component.stop(); + component = null; + super.tearDown(); + } + /** + * Tests the actions. + */ + public void testAction() { + FunctionService service = new FunctionService(); + List values = null; + try { + List doubleList = new ArrayList(); + doubleList.add(240.0); + doubleList.add(450.0); + values = service.convertDoubleArray("1", "2", doubleList, new Double(1D)); + } catch (Exception e) { + Context.getCurrentLogger().warning( + "Exception occurred while calling a function: " + e.getMessage()); + Assert.fail(); + } + assertNotNull(values); + assertTrue(values.size()>0); + assertEquals("20.0", values.get(0)); + assertEquals("65.6", values.get(1)); + } + +} diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/function/FunctionService.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/function/FunctionService.java new file mode 100644 index 0000000000..aa91172991 --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/function/FunctionService.java @@ -0,0 +1,85 @@ +package org.restlet.test.ext.odata.function; + +import java.util.ArrayList; +import java.util.List; + +import org.restlet.data.Parameter; +import org.restlet.ext.odata.Service; +import org.restlet.ext.odata.internal.AtomContentFunctionHandler; +import org.restlet.ext.odata.internal.FunctionContentHandler; +import org.restlet.ext.odata.internal.JsonContentFunctionHandler; +import org.restlet.representation.Representation; +import org.restlet.util.Series; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +/** + * Generated for the WCF Data Services extension for the + * Restlet framework.
+ */ +public class FunctionService extends Service { + + FunctionContentHandler functionContentHandler; + + /** + * Constructor. + */ + public FunctionService() { + super("http://localhost:8111/Unit.svc"); + this.functionContentHandler = new JsonContentFunctionHandler(); + } + + public FunctionService(FunctionContentHandler functionContentHandler) { + super("http://localhost:8111/Unit.svc"); + this.functionContentHandler = functionContentHandler; + } + + public Nextval_t nextval(String tableName) { + Nextval_t nextval = null; + Series parameters = new Series(Parameter.class); + Parameter paramtableName = new Parameter(); + paramtableName.setName("tableName"); + paramtableName.setValue(tableName.toString()); + parameters.add(paramtableName); + Representation representation = invokeComplex("nextval", parameters); + + nextval = (Nextval_t) functionContentHandler.parseResult( + Nextval_t.class, representation, "nextval", null); + return nextval; + } + + @SuppressWarnings("unchecked") + public List convertDoubleArray(String from, String to, + List value, Double nullValue) { + List convertDoubleArray = new ArrayList(); + Series parameters = new Series(Parameter.class); + Parameter paramfrom = new Parameter(); + paramfrom.setName("from"); + paramfrom.setValue(from.toString()); + parameters.add(paramfrom); + Parameter paramto = new Parameter(); + paramto.setName("to"); + paramto.setValue(to.toString()); + parameters.add(paramto); + Parameter paramvalue = new Parameter(); + paramvalue.setName("value"); + Gson gson = new GsonBuilder().serializeNulls() + .serializeSpecialFloatingPointValues().create(); + String jsonValue = gson.toJson(value); + paramvalue.setValue(jsonValue); + parameters.add(paramvalue); + Parameter paramnullValue = new Parameter(); + paramnullValue.setName("nullValue"); + paramnullValue.setValue(nullValue.toString()); + parameters.add(paramnullValue); + Representation representation = invokeComplex("convertDoubleArray", + parameters); + AtomContentFunctionHandler atomContentFunctionHandler = new AtomContentFunctionHandler(); + + convertDoubleArray = (List) atomContentFunctionHandler + .parseResult(convertDoubleArray.getClass(), representation, + "convertDoubleArray", convertDoubleArray); + return convertDoubleArray; + } +} diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/function/FunctionTestCase.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/function/FunctionTestCase.java new file mode 100644 index 0000000000..1652ac9a09 --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/function/FunctionTestCase.java @@ -0,0 +1,59 @@ +package org.restlet.test.ext.odata.function; + + +import junit.framework.Assert; + +import org.restlet.Component; +import org.restlet.Context; +import org.restlet.data.Protocol; +import org.restlet.test.RestletTestCase; + +/** + * Test case for function for Restlet. + */ +public class FunctionTestCase extends RestletTestCase { + + /** Inner component. */ + private Component component = new Component(); + + /** OData service used for all tests. */ + private FunctionService service; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + component.getServers().add(Protocol.HTTP, 8111); + component.getClients().add(Protocol.CLAP); + component.getDefaultHost().attach("/Unit.svc", + new UnitApplication()); + component.start(); + + service = new FunctionService(); + } + + @Override + protected void tearDown() throws Exception { + component.stop(); + component = null; + super.tearDown(); + } + + /** + * Tests the function to return correct value. + */ + public void testFunction() { + FunctionService service = new FunctionService(); + Nextval_t nextval = null; + try { + nextval = service.nextval("RCompany"); + } catch (Exception e) { + Context.getCurrentLogger().warning( + "Exception occurred while calling a function: " + e.getMessage()); + Assert.fail(); + } + assertEquals("534", nextval.getSysGenId().toString()); + + } + +} diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/function/Nextval_t.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/function/Nextval_t.java new file mode 100644 index 0000000000..2d08404973 --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/function/Nextval_t.java @@ -0,0 +1,39 @@ +package org.restlet.test.ext.odata.function; + + +/** +* Generated for the OData extension for the Restlet framework.
+*/ +public class Nextval_t { + + private Integer sysGenId; + + /** + * Constructor without parameter. + * + */ + public Nextval_t() { + super(); + } + + /** + * Returns the value of the "sysGenId" attribute. + * + * @return The value of the "sysGenId" attribute. + */ + public Integer getSysGenId() { + return sysGenId; + } + + + /** + * Sets the value of the "sysGenId" attribute. + * + * @param sysGenId + * The value of the "sysGenId" attribute. + */ + public void setSysGenId(Integer sysGenId) { + this.sysGenId = sysGenId; + } + +} \ No newline at end of file diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/function/UnitApplication.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/function/UnitApplication.java new file mode 100644 index 0000000000..71d31613cf --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/function/UnitApplication.java @@ -0,0 +1,76 @@ +package org.restlet.test.ext.odata.function; + +import org.restlet.Application; +import org.restlet.Context; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.Restlet; +import org.restlet.data.CharacterSet; +import org.restlet.data.LocalReference; +import org.restlet.data.Method; +import org.restlet.data.Protocol; +import org.restlet.routing.Router; + +/** + * Sample application that simulates the "Unit" service. + */ +public class UnitApplication extends Application { + + private static class MyClapRestlet extends Restlet { + String file; + + boolean updatable; + + public MyClapRestlet(Context context, String file, boolean updatable) { + super(context); + this.file = file; + this.updatable = updatable; + } + + @Override + public void handle(Request request, Response response) { + if (Method.GET.equals(request.getMethod())) { + String uri = "/" + + this.getClass().getPackage().getName() + .replace(".", "/") + "/" + file; + + Response r = getContext().getClientDispatcher().handle( + new Request(Method.GET, LocalReference + .createClapReference(LocalReference.CLAP_CLASS, + uri + ".xml"))); + response.setEntity(r.getEntity()); + response.setStatus(r.getStatus()); + + } else if (Method.POST.equals(request.getMethod())) { + System.out.println(); + String uri = "/" + + this.getClass().getPackage().getName() + .replace(".", "/") + "/" + file; + Response r = getContext().getClientDispatcher().handle( + new Request(Method.GET, LocalReference + .createClapReference(LocalReference.CLAP_CLASS, + uri + ".xml"))); + response.setEntity(r.getEntity()); + response.setStatus(r.getStatus()); + } + + } + } + + @Override + public Restlet createInboundRoot() { + getMetadataService().setDefaultCharacterSet(CharacterSet.ISO_8859_1); + getConnectorService().getClientProtocols().add(Protocol.CLAP); + Router router = new Router(getContext()); + + router.attach("/$metadata", new MyClapRestlet(getContext(), "metadata", + true)); + router.attach("/nextval", new MyClapRestlet(getContext(), "nextval", + true)); + router.attach("/convertDoubleArray", new MyClapRestlet(getContext(), + "convertDoubleArray", true)); + + return router; + } + +} diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/function/convertDoubleArray.xml b/modules/org.restlet.test/src/org/restlet/test/ext/odata/function/convertDoubleArray.xml new file mode 100644 index 0000000000..08160646ff --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/function/convertDoubleArray.xml @@ -0,0 +1,2 @@ + +20.065.6 \ No newline at end of file diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/function/metadata.xml b/modules/org.restlet.test/src/org/restlet/test/ext/odata/function/metadata.xml new file mode 100644 index 0000000000..7286e15812 --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/function/metadata.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + Convert float[] or double[] between 2 units. + It will return converted value back + + + + + the unit to be converted from + it can be unit id or unit name. + + + + + + the unit to be converted to + it can be unit id or unit name. + + + + + + the value to be converted + It can be float[] or doulble[]. + + + + + + Defines the number that will be treated as null value + + Optional. If it is defined, then any element in + the + value array whose value equals to it will not be converted. + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/function/nextval.xml b/modules/org.restlet.test/src/org/restlet/test/ext/odata/function/nextval.xml new file mode 100644 index 0000000000..37282c25e0 --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/function/nextval.xml @@ -0,0 +1,2 @@ + +[ { "SysGenId" : 534 } ] \ No newline at end of file diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/streamcrud/Cafe.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/streamcrud/Cafe.java new file mode 100644 index 0000000000..e8fb3c7aa2 --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/streamcrud/Cafe.java @@ -0,0 +1,178 @@ +package org.restlet.test.ext.odata.streamcrud; + +import org.restlet.data.Reference; +import org.restlet.ext.odata.streaming.StreamReference; + +/** + * Generated by the generator tool for the WCF Data Services extension for the + * Restlet framework.
+ * + * @see Metadata of the + * target WCF Data Services + * + */ +public class Cafe { + + private String city; + private String id; + private String name; + private int zipCode; + private StreamReference attachment; + /** The reference of the underlying blob representation. */ + private Reference blobReference; + /** The reference to update the underlying blob representation. */ + private Reference blobEditReference; + + /** + * Constructor without parameter. + * + */ + public Cafe() { + super(); + } + + /** + * Constructor. + * + * @param id + * The identifiant value of the entity. + */ + public Cafe(String id) { + this(); + this.id = id; + } + + /** + * Returns the value of the city attribute. + * + * @return The value of the city attribute. + */ + public String getCity() { + return city; + } + + /** + * Returns the value of the id attribute. + * + * @return The value of the id attribute. + */ + public String getId() { + return id; + } + + /** + * Returns the value of the name attribute. + * + * @return The value of the name attribute. + */ + public String getName() { + return name; + } + + /** + * Returns the value of the zipCode attribute. + * + * @return The value of the zipCode attribute. + */ + public int getZipCode() { + return zipCode; + } + + /** + * Sets the value of the city attribute. + * + * @param City + * The value of the city attribute. + */ + public void setCity(String city) { + this.city = city; + } + + /** + * Sets the value of the id attribute. + * + * @param ID + * The value of the id attribute. + */ + public void setId(String id) { + this.id = id; + } + + /** + * Sets the value of the name attribute. + * + * @param Name + * The value of the name attribute. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Sets the value of the zipCode attribute. + * + * @param ZipCode + * The value of the zipCode attribute. + */ + public void setZipCode(int zipCode) { + this.zipCode = zipCode; + } + + /** + * Returns the @{Link Reference} of the underlying blob. + * + * @return The @{Link Reference} of the underlying blob. + */ + public Reference getBlobReference() { + return blobReference; + } + + /** + * Returns the @{Link Reference} to update the underlying blob. + * + * @return The @{Link Reference} to update the underlying blob. + */ + public Reference getBlobEditReference() { + return blobEditReference; + } + + /** + * Sets the value of the "attachment" attribute. + * + * @param attachment + * The value of the "attachment" attribute. + */ + public StreamReference getAttachment() { + return attachment; + } + + /** + * Sets the @{Link Reference} of the underlying blob. + * + * @param ref + * The @{Link Reference} of the underlying blob. + */ + public void setBlobReference(Reference ref) { + this.blobReference = ref; + } + + /** + * Sets the @{Link Reference} to update the underlying blob. + * + * @param ref + * The @{Link Reference} to update the underlying blob. + */ + public void setBlobEditReference(Reference ref) { + this.blobEditReference = ref; + } + + /** + * Sets the value of the "attachment" attribute. + * + * @param attachment + * The value of the "attachment" attribute. + */ + public void setAttachment(StreamReference attachment) { + this.attachment = attachment; + } +} diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/streamcrud/CafeCrudApplication.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/streamcrud/CafeCrudApplication.java new file mode 100644 index 0000000000..1443a73ffd --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/streamcrud/CafeCrudApplication.java @@ -0,0 +1,124 @@ +package org.restlet.test.ext.odata.streamcrud; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.concurrent.ConcurrentMap; + +import org.restlet.Application; +import org.restlet.Context; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.Restlet; +import org.restlet.data.CharacterSet; +import org.restlet.data.LocalReference; +import org.restlet.data.MediaType; +import org.restlet.data.Method; +import org.restlet.data.Protocol; +import org.restlet.data.Status; +import org.restlet.engine.header.HeaderConstants; +import org.restlet.representation.InputRepresentation; +import org.restlet.representation.Representation; +import org.restlet.routing.Router; +import org.restlet.util.NamedValue; +import org.restlet.util.Series; + +/** + * Sample application that simulates the CUD operation on MLE entities. + * + */ +public class CafeCrudApplication extends Application { + + @SuppressWarnings("unused") + private static class MyClapRestlet extends Restlet { + String file; + boolean updatable; + + public MyClapRestlet(Context context, String file, boolean updatable) { + super(context); + this.file = file; + this.updatable = updatable; + } + + @Override + @SuppressWarnings("unchecked") + public void handle(Request request, Response response) { + if (Method.GET.equals(request.getMethod())) { + + String uri = "/" + + this.getClass().getPackage().getName() + .replace(".", "/") + "/" + file; + //check for request is for MLE + if (request.getOriginalRef().toString().contains("$value")) { + InputStream inputStream = null; + String str = "TEST"; + // convert String into InputStream + inputStream = new ByteArrayInputStream(str.getBytes()); + + Representation result = new InputRepresentation( + inputStream, MediaType.APPLICATION_OCTET_STREAM); + response.setEntity(result); + response.setStatus(Status.SUCCESS_OK); + + } else { + Response r = getContext().getClientDispatcher().handle( + new Request(Method.GET, LocalReference + .createClapReference( + LocalReference.CLAP_CLASS, uri + + ".xml"))); + response.setEntity(r.getEntity()); + response.setStatus(r.getStatus()); + } + } else if (Method.POST.equals(request.getMethod()) + || Method.PUT.equals(request.getMethod())) { + // read request header to get slug header and also to get merge + // request + ConcurrentMap attributes = request + .getAttributes(); + Series> series = (Series>) attributes + .get("org.restlet.http.headers"); + NamedValue first =series + .getFirst(HeaderConstants.HEADER_SLUG); + Representation entity = request.getEntity(); + if (first != null) { // contains slug header + if(Method.PUT.equals(request.getMethod())){ + response.setStatus(Status.SUCCESS_OK); + } else { + response.setStatus(Status.SUCCESS_CREATED); + } + } else { + first = series.getFirst(HeaderConstants.HEADER_X_HTTP_METHOD, true); + if (first != null) {// check for merge request + try { + entity.getText(); + response.setStatus(Status.SUCCESS_OK); + } catch (Exception e) { + response.setStatus(Status.CLIENT_ERROR_BAD_REQUEST); + } + } + } + if (first == null) { + response.setStatus(Status.CLIENT_ERROR_BAD_REQUEST); + } + } else if (Method.DELETE.equals(request.getMethod())) { + response.setStatus(Status.SUCCESS_NO_CONTENT); + } + } + } + + @Override + public Restlet createInboundRoot() { + getMetadataService().setDefaultCharacterSet(CharacterSet.ISO_8859_1); + getConnectorService().getClientProtocols().add(Protocol.CLAP); + Router router = new Router(getContext()); + + router.attach("/$metadata", new MyClapRestlet(getContext(), "metadata", + true)); + router.attach("/Cafes", new MyClapRestlet(getContext(), "cafes", true)); + router.attach("/Cafes('30')", new MyClapRestlet(getContext(), "cafes_Updated", + true)); + router.attach("/Cafes('30')/$value", new MyClapRestlet(getContext(), + "streamData", true)); + return router; + } + +} diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/streamcrud/CafeService.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/streamcrud/CafeService.java new file mode 100644 index 0000000000..58ea0686bb --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/streamcrud/CafeService.java @@ -0,0 +1,44 @@ +package org.restlet.test.ext.odata.streamcrud; + +import org.restlet.ext.odata.Query; +import org.restlet.ext.odata.Service; + +/** +* Generated by the generator tool for the WCF Data Services extension for the Restlet framework.
+* +* @see Metadata of the target WCF Data Services +* +*/ +public class CafeService extends Service { + + /** + * Constructor. + * + */ + public CafeService() { + super("http://localhost:8111/Cafe.svc"); + } + + /** + * Adds a new entity to the service. + * + * @param entity + * The entity to add to the service. + * @throws Exception + */ + public void addEntity(Cafe entity) throws Exception { + addEntity("/Cafes", entity); + } + + /** + * Creates a query for cafe entities hosted by this service. + * + * @param subpath + * The path to this entity relatively to the service URI. + * @return A query object. + */ + public Query createCafeQuery(String subpath) { + return createQuery(subpath, Cafe.class); + } + +} diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/streamcrud/ODataCafeCrudStreamTestCase.java b/modules/org.restlet.test/src/org/restlet/test/ext/odata/streamcrud/ODataCafeCrudStreamTestCase.java new file mode 100644 index 0000000000..4cc70f2257 --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/streamcrud/ODataCafeCrudStreamTestCase.java @@ -0,0 +1,153 @@ +package org.restlet.test.ext.odata.streamcrud; + + + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import junit.framework.Assert; + +import org.restlet.Component; +import org.restlet.Context; +import org.restlet.Response; +import org.restlet.data.Protocol; +import org.restlet.data.Status; +import org.restlet.ext.odata.Query; +import org.restlet.ext.odata.streaming.StreamReference; +import org.restlet.test.RestletTestCase; + +/** + * Test case for OData service for CUD operation on entities. + * + */ +@SuppressWarnings("unused") +public class ODataCafeCrudStreamTestCase extends RestletTestCase { + + /** Inner component. */ + private Component component = new Component(); + + /** OData service used for all tests. */ + private CafeService service; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + component.getServers().add(Protocol.HTTP, 8111); + component.getClients().add(Protocol.CLAP); + component.getDefaultHost().attach("/Cafe.svc", + new CafeCrudApplication()); + component.start(); + + service = new CafeService(); + } + + @Override + protected void tearDown() throws Exception { + component.stop(); + component = null; + super.tearDown(); + } + + + public void testStreamingCrudCafe() { + CafeService service = new CafeService(); + + //create. + Cafe cafe = new Cafe(); + cafe.setId("30"); + cafe.setName("TestName"); + cafe.setCity("TestCity"); + cafe.setZipCode(111111); + String contentType="application/octet-stream"; + String str = "TEST"; + // convert String into InputStream + InputStream inputStream = new ByteArrayInputStream(str.getBytes()); + StreamReference attachment =null; + try { + attachment = new StreamReference(contentType, inputStream); + cafe.setAttachment(attachment); + service.addEntity(cafe); + } catch (Exception e) { + Context.getCurrentLogger().warning( + "Exception occurred while adding entity: " + e.getMessage()); + Assert.fail(); + } + + //read. + Query query = service.createCafeQuery("/Cafes"); + Cafe cafe1 = query.iterator().next(); + Response latestResponse = query.getService().getLatestResponse(); + assertEquals(Status.SUCCESS_OK, latestResponse.getStatus()); + assertEquals("TestName", cafe1.getName()); + assertEquals("30", cafe1.getId()); + assertEquals(111111, cafe1.getZipCode()); + attachment = cafe1.getAttachment(); + assertEquals(contentType, attachment.getContentType()); + try { + inputStream= attachment.getInputStream(service); + } catch (IOException e) { + Context.getCurrentLogger().warning( + "Exception occurred while reading input stream: " + e.getMessage()); + Assert.fail(); + } + assertNotNull(inputStream); + + + //// Update. + cafe1.setName("TestName-update"); + attachment = cafe1.getAttachment(); + str = "TEST-udate"; + // convert String into InputStream + inputStream = new ByteArrayInputStream(str.getBytes()); + attachment.setInputStream(inputStream); + //mandatory property if upadating stream + attachment.setUpdateStreamData(true); + cafe1.setAttachment(attachment); + + try { + service.updateEntity(cafe1); + } catch (Exception e) { + Context.getCurrentLogger().warning( + "Exception occurred while updating entity: " + e.getMessage()); + Assert.fail(); + } + + + // read. + Query query3 = service.createCafeQuery("/Cafes('30')"); + Cafe cafe2 = query3.iterator().next(); + latestResponse = query3.getService().getLatestResponse(); + assertEquals(Status.SUCCESS_OK, latestResponse.getStatus()); + assertEquals("TestName-update", cafe2.getName()); + assertEquals("30", cafe2.getId()); + assertEquals(111111, cafe2.getZipCode()); + attachment = cafe2.getAttachment(); + assertEquals(contentType, attachment.getContentType()); + try { + inputStream = attachment.getInputStream(service); + } catch (IOException e) { + Context.getCurrentLogger().warning( + "Exception occurred while fetching inputstream: " + e.getMessage()); + Assert.fail(); + } + assertNotNull(inputStream); + + // Delete + Query query4 = service.createCafeQuery("/Cafes"); + Cafe cafe3 = query4.iterator().next(); + try { + service.deleteEntity(cafe3); + } catch (Exception e) { + Context.getCurrentLogger().warning( + "Exception occurred while deleting entity: " + e.getMessage()); + Assert.fail(); + } + latestResponse = query4.getService().getLatestResponse(); + assertEquals(Status.SUCCESS_NO_CONTENT, latestResponse.getStatus()); + + + } + +} diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/streamcrud/cafes.xml b/modules/org.restlet.test/src/org/restlet/test/ext/odata/streamcrud/cafes.xml new file mode 100644 index 0000000000..2d33602ed6 --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/streamcrud/cafes.xml @@ -0,0 +1,29 @@ + + + Cafes + http://localhost:8111/Cafe.svc/Cafes + 2010-02-17T11:28:13Z + + + http://localhost:8111/Cafe.svc/Cafes('30') + + 2010-02-17T11:28:13Z + + + + + + + + 30 + TestName + 111111 + TestCity + + + + \ No newline at end of file diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/streamcrud/cafes_Updated.xml b/modules/org.restlet.test/src/org/restlet/test/ext/odata/streamcrud/cafes_Updated.xml new file mode 100644 index 0000000000..9c65db357e --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/streamcrud/cafes_Updated.xml @@ -0,0 +1,29 @@ + + + Cafes + http://localhost:8111/Cafe.svc/Cafes + 2010-02-17T11:28:13Z + + + http://localhost:8111/Cafe.svc/Cafes('30') + + 2010-02-17T11:28:13Z + + + + + + + + 30 + TestName-update + 111111 + TestCity + + + + \ No newline at end of file diff --git a/modules/org.restlet.test/src/org/restlet/test/ext/odata/streamcrud/metadata.xml b/modules/org.restlet.test/src/org/restlet/test/ext/odata/streamcrud/metadata.xml new file mode 100644 index 0000000000..67d1e019aa --- /dev/null +++ b/modules/org.restlet.test/src/org/restlet/test/ext/odata/streamcrud/metadata.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/org.restlet/src/org/restlet/data/ChallengeScheme.java b/modules/org.restlet/src/org/restlet/data/ChallengeScheme.java index 09e8e8efde..da35624598 100644 --- a/modules/org.restlet/src/org/restlet/data/ChallengeScheme.java +++ b/modules/org.restlet/src/org/restlet/data/ChallengeScheme.java @@ -92,6 +92,10 @@ public final class ChallengeScheme { /** Cookie HTTP scheme. */ public static final ChallengeScheme HTTP_COOKIE = new ChallengeScheme( "HTTP_Cookie", "Cookie", "Cookie HTTP authentication"); + + /** HTTP Negotiate scheme. */ + public static final ChallengeScheme HTTP_NEGOTIATE = new ChallengeScheme( + "HTTP_NEGOTIATE", "Negotiate", "HTTP Negotiate authentication"); /** Digest HTTP scheme. */ public static final ChallengeScheme HTTP_DIGEST = new ChallengeScheme( @@ -152,6 +156,7 @@ public final class ChallengeScheme { HTTP_AZURE_SHAREDKEY_LITE); schemes.put(HTTP_BASIC.getName().toLowerCase(), HTTP_BASIC); schemes.put(HTTP_COOKIE.getName().toLowerCase(), HTTP_COOKIE); + schemes.put(HTTP_NEGOTIATE.getName().toLowerCase(), HTTP_NEGOTIATE); schemes.put(HTTP_DIGEST.getName().toLowerCase(), HTTP_DIGEST); schemes.put(HTTP_NTLM.getName().toLowerCase(), HTTP_NTLM); schemes.put(HTTP_OAUTH.getName().toLowerCase(), HTTP_OAUTH); diff --git a/modules/org.restlet/src/org/restlet/data/MediaType.java b/modules/org.restlet/src/org/restlet/data/MediaType.java index c0e974cb03..fb0299f531 100644 --- a/modules/org.restlet/src/org/restlet/data/MediaType.java +++ b/modules/org.restlet/src/org/restlet/data/MediaType.java @@ -588,6 +588,11 @@ public final class MediaType extends Metadata { public static final MediaType MULTIPART_ALL = register("multipart/*", "All multipart data"); + // [ifndef gwt] member + public static final MediaType MULTIPART_MIXED = register("multipart/mixed", + "Mixed multipart data"); + + // [ifndef gwt] member public static final MediaType MULTIPART_FORM_DATA = register( "multipart/form-data", "Multipart form data"); diff --git a/modules/org.restlet/src/org/restlet/engine/header/HeaderConstants.java b/modules/org.restlet/src/org/restlet/engine/header/HeaderConstants.java index f7acda7c0a..336bbaa16c 100644 --- a/modules/org.restlet/src/org/restlet/engine/header/HeaderConstants.java +++ b/modules/org.restlet/src/org/restlet/engine/header/HeaderConstants.java @@ -202,4 +202,6 @@ public final class HeaderConstants { public static final String ATTRIBUTE_HTTPS_KEY_SIZE = "org.restlet.https.keySize"; public static final String ATTRIBUTE_HTTPS_SSL_SESSION_ID = "org.restlet.https.sslSessionId"; + + public static final String HEADER_X_HTTP_METHOD = "X-HTTP-METHOD"; } diff --git a/modules/org.restlet/src/org/restlet/engine/header/HeaderUtils.java b/modules/org.restlet/src/org/restlet/engine/header/HeaderUtils.java index 5682dde193..313f2ab477 100644 --- a/modules/org.restlet/src/org/restlet/engine/header/HeaderUtils.java +++ b/modules/org.restlet/src/org/restlet/engine/header/HeaderUtils.java @@ -163,7 +163,11 @@ public static void addEntityHeaders(Representation entity, addHeader(HeaderConstants.HEADER_CONTENT_LOCATION, entity .getLocationRef().getTargetRef().toString(), headers); } - + + if (entity.isX_http_header()) { + addHeader(HeaderConstants.HEADER_X_HTTP_METHOD, "MERGE", headers); + } + // [ifndef gwt] if (entity.getDigest() != null && Digest.ALGORITHM_MD5.equals(entity.getDigest() @@ -208,6 +212,11 @@ public static void addEntityHeaders(Representation entity, DispositionWriter.write(entity.getDisposition()), headers); } + + if (entity.getSlugHeader() != null) { + addHeader(HeaderConstants.HEADER_SLUG, + entity.getSlugHeader(), headers); + } } } diff --git a/modules/org.restlet/src/org/restlet/representation/Representation.java b/modules/org.restlet/src/org/restlet/representation/Representation.java index c4279c76da..6e3518e1df 100644 --- a/modules/org.restlet/src/org/restlet/representation/Representation.java +++ b/modules/org.restlet/src/org/restlet/representation/Representation.java @@ -112,6 +112,10 @@ public abstract class Representation extends RepresentationInfo { */ private volatile long size; + private volatile String slugHeader; + + private volatile boolean x_http_header; + /** * Default constructor. */ @@ -132,6 +136,8 @@ public Representation(MediaType mediaType) { this.isTransient = false; this.size = UNKNOWN_SIZE; this.expirationDate = null; + this.setSlugHeader(null); + this.setX_http_header(false); // [ifndef gwt] this.digest = null; this.range = null; @@ -622,6 +628,40 @@ public void setSize(long expectedSize) { } /** + * @return the slugHeader + */ + public String getSlugHeader() { + return slugHeader; + } + + /** + * @param slugHeader + * the slugHeader to set + */ + public void setSlugHeader(String slugHeader) { + this.slugHeader = slugHeader; + } + + /** + * @return the x_http_header + * true if the request is for merge. + */ + public boolean isX_http_header() { + return x_http_header; + } + + + /** + * Indicates if the request is for merge. + * + * @param x_http_header + * True if the request is for merge. + */ + public void setX_http_header(boolean x_http_header) { + this.x_http_header = x_http_header; + } + + /** * Indicates if the representation's content is transient. * * @param isTransient diff --git a/modules/org.restlet/src/org/restlet/resource/ClientResource.java b/modules/org.restlet/src/org/restlet/resource/ClientResource.java index b446eee3ea..ceff0fc063 100644 --- a/modules/org.restlet/src/org/restlet/resource/ClientResource.java +++ b/modules/org.restlet/src/org/restlet/resource/ClientResource.java @@ -57,6 +57,7 @@ import org.restlet.data.Range; import org.restlet.data.Reference; import org.restlet.data.Status; +import org.restlet.representation.EmptyRepresentation; import org.restlet.representation.Representation; import org.restlet.representation.StringRepresentation; import org.restlet.representation.Variant; @@ -1483,6 +1484,37 @@ public Representation post(Object entity) throws ResourceException { } } + /** + * Posts an object entity. Automatically serializes the object using the + * {@link org.restlet.service.ConverterService}. + * + * @param entity + * The object entity to post. + * @param slugHeader + * the slug header. For Streaming, we require to send mandatory field's value in Slug header for create + * operation. + * @param contentType + * the content type + * @return The optional result entity. + * @throws ResourceException + * the resource exception. + */ + public Representation post(Object entity, String slugHeader, + String contentType) throws ResourceException { + Representation representation = null; + try { + representation = toRepresentation(entity); + if (null != contentType) {// set the contentType + representation.setMediaType(new MediaType(contentType)); + } + representation.setSlugHeader(slugHeader); + + } catch (IOException e) { + throw new ResourceException(e); + } + return post(representation); + } + // [ifndef gwt] method /** * Posts an object entity. Automatically serializes the object using the @@ -1542,6 +1574,20 @@ public Representation post(Representation entity) throws ResourceException { return handle(Method.POST, entity); } + + /** + * Creates a merge request. + * + * @param entity the entity + * @return the representation + * @throws ResourceException the resource exception + */ + public Representation merge(Representation entity) throws ResourceException { + entity.setX_http_header(true); // Need to set this to true so that header contains X-http header as merge. + return handle(Method.POST, entity); // create post request with X-http header as merge + } + + /** * Puts an object entity. Automatically serializes the object using the * {@link org.restlet.service.ConverterService}. @@ -2132,4 +2178,33 @@ public T wrap(Class resourceInterface, return result; } + + /** + * Put request with slug header for stream data update + * + * @param entity + * the entity + * @param slugHeader + * the slug header + * @param contentType + * the content type + * @return the representation + */ + public Representation put(Object entity, String slugHeader, + String contentType) { + Representation representation = null; + try { + representation = toRepresentation(entity); + if (null == representation) { + representation = new EmptyRepresentation(); + } + if (null != contentType) {// set the contentType + representation.setMediaType(new MediaType(contentType)); + } + representation.setSlugHeader(slugHeader); + } catch (IOException e) { + throw new ResourceException(e); + } + return put(representation); + } } From 8e0401f7c8a3012538bb588a895bb5bf579d3b9b Mon Sep 17 00:00:00 2001 From: Onkar Dhuri Date: Wed, 13 Aug 2014 15:21:24 +0530 Subject: [PATCH 2/2] reverted classpath --- modules/org.restlet.ext.atom/.classpath | 2 +- modules/org.restlet.ext.crypto/.classpath | 2 +- modules/org.restlet.ext.freemarker/.classpath | 4 ++-- modules/org.restlet.ext.httpclient/.classpath | 16 ++++++++-------- modules/org.restlet.ext.odata/.classpath | 5 ++--- modules/org.restlet.ext.xml/.classpath | 3 +-- modules/org.restlet/.classpath | 5 ++--- 7 files changed, 17 insertions(+), 20 deletions(-) diff --git a/modules/org.restlet.ext.atom/.classpath b/modules/org.restlet.ext.atom/.classpath index d0653d85bc..437176e6ab 100644 --- a/modules/org.restlet.ext.atom/.classpath +++ b/modules/org.restlet.ext.atom/.classpath @@ -1,8 +1,8 @@ + - diff --git a/modules/org.restlet.ext.crypto/.classpath b/modules/org.restlet.ext.crypto/.classpath index 46b0ccfa71..07fcd75d7f 100644 --- a/modules/org.restlet.ext.crypto/.classpath +++ b/modules/org.restlet.ext.crypto/.classpath @@ -1,7 +1,7 @@ + - diff --git a/modules/org.restlet.ext.freemarker/.classpath b/modules/org.restlet.ext.freemarker/.classpath index 69ed219193..019f0b314e 100644 --- a/modules/org.restlet.ext.freemarker/.classpath +++ b/modules/org.restlet.ext.freemarker/.classpath @@ -1,8 +1,8 @@ + - - + diff --git a/modules/org.restlet.ext.httpclient/.classpath b/modules/org.restlet.ext.httpclient/.classpath index 127b8d3630..4d1ad1ffb3 100644 --- a/modules/org.restlet.ext.httpclient/.classpath +++ b/modules/org.restlet.ext.httpclient/.classpath @@ -1,14 +1,14 @@ + - - - - - - - - + + + + + + + diff --git a/modules/org.restlet.ext.odata/.classpath b/modules/org.restlet.ext.odata/.classpath index dc5b758d7d..a77e42e457 100644 --- a/modules/org.restlet.ext.odata/.classpath +++ b/modules/org.restlet.ext.odata/.classpath @@ -1,12 +1,11 @@ + - - - + diff --git a/modules/org.restlet.ext.xml/.classpath b/modules/org.restlet.ext.xml/.classpath index e3e781b027..07fcd75d7f 100644 --- a/modules/org.restlet.ext.xml/.classpath +++ b/modules/org.restlet.ext.xml/.classpath @@ -1,8 +1,7 @@ - + - diff --git a/modules/org.restlet/.classpath b/modules/org.restlet/.classpath index db351fc526..7c020dbd63 100644 --- a/modules/org.restlet/.classpath +++ b/modules/org.restlet/.classpath @@ -1,8 +1,7 @@ - - - + +