diff --git a/fit/src/main/java/org/apache/olingo/fit/AbstractServices.java b/fit/src/main/java/org/apache/olingo/fit/AbstractServices.java index 025e4fed4..1ffba9485 100644 --- a/fit/src/main/java/org/apache/olingo/fit/AbstractServices.java +++ b/fit/src/main/java/org/apache/olingo/fit/AbstractServices.java @@ -54,6 +54,7 @@ import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import org.apache.commons.codec.binary.Base64; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.olingo.fit.utils.Constants; @@ -144,6 +145,40 @@ public abstract class AbstractServices { } } + /** + * Retrieve entity reference sample. + * + * @param accept Accept header. + * @param path path. + * @param format format query option. + * @return entity reference or feed of entity reference. + */ + @GET + @Path("/{path:.*}/$ref") + public Response getEntityReference( + @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) String accept, + @PathParam("path") String path, + @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) String format) { + + try { + final Map.Entry utils = getUtilities(accept, format); + + if (utils.getKey() == Accept.TEXT) { + throw new UnsupportedMediaTypeException("Unsupported media type"); + } + + final String filename = Base64.encodeBase64String(path.getBytes("UTF-8")); + + return utils.getValue().createResponse( + FSManager.instance(getVersion()).readFile(Constants.REF + File.separatorChar + filename, utils.getKey()), + null, + utils.getKey()); + } catch (Exception e) { + LOG.error("Error retrieving entity", e); + return xml.createFaultResponse(accept, e); + } + } + @MERGE @Path("/{entitySetName}/{entityId}") @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON}) @@ -386,7 +421,7 @@ public abstract class AbstractServices { } else if (StringUtils.isNotBlank(skiptoken)) { builder.append(SKIP_TOKEN).append(File.separatorChar).append(skiptoken); } else { - builder.append(FEED); + builder.append(Commons.getLinkInfo().get(getVersion()).isSingleton(name) ? ENTITY : FEED); } InputStream feed = FSManager.instance(getVersion()).readFile(builder.toString(), acceptType); diff --git a/fit/src/main/java/org/apache/olingo/fit/utils/AbstractXMLUtilities.java b/fit/src/main/java/org/apache/olingo/fit/utils/AbstractXMLUtilities.java index d02e8289a..cef564a5a 100644 --- a/fit/src/main/java/org/apache/olingo/fit/utils/AbstractXMLUtilities.java +++ b/fit/src/main/java/org/apache/olingo/fit/utils/AbstractXMLUtilities.java @@ -1208,11 +1208,21 @@ public abstract class AbstractXMLUtilities extends AbstractUtilities { writer.add(eventFactory.createStartDocument("UTF-8", "1.0")); writer.add(property.getStart()); - if (property.getStart().getAttributeByName(new QName(ATOM_DATASERVICE_NS)) == null) { - writer.add(eventFactory.createNamespace(ATOM_PROPERTY_PREFIX.substring(0, 1), DATASERVICES_NS)); - } - if (property.getStart().getAttributeByName(new QName(ATOM_METADATA_NS)) == null) { - writer.add(eventFactory.createNamespace(ATOM_METADATA_PREFIX.substring(0, 1), METADATA_NS)); + if (version == ODataVersion.v4) { + + if (property.getStart().getAttributeByName(new QName(ATOM_DATASERVICE_NS)) == null) { + writer.add(eventFactory.createNamespace(ATOM_PROPERTY_PREFIX.substring(0, 1), V4_DATASERVICES_NS)); + } + if (property.getStart().getAttributeByName(new QName(ATOM_METADATA_NS)) == null) { + writer.add(eventFactory.createNamespace(ATOM_METADATA_PREFIX.substring(0, 1), V4_METADATA_NS)); + } + } else { + if (property.getStart().getAttributeByName(new QName(ATOM_DATASERVICE_NS)) == null) { + writer.add(eventFactory.createNamespace(ATOM_PROPERTY_PREFIX.substring(0, 1), V3_DATASERVICES_NS)); + } + if (property.getStart().getAttributeByName(new QName(ATOM_METADATA_NS)) == null) { + writer.add(eventFactory.createNamespace(ATOM_METADATA_PREFIX.substring(0, 1), V3_METADATA_NS)); + } } writer.add(property.getContentReader()); diff --git a/fit/src/main/java/org/apache/olingo/fit/utils/Commons.java b/fit/src/main/java/org/apache/olingo/fit/utils/Commons.java index dcc48074b..4cf992ac1 100644 --- a/fit/src/main/java/org/apache/olingo/fit/utils/Commons.java +++ b/fit/src/main/java/org/apache/olingo/fit/utils/Commons.java @@ -41,6 +41,7 @@ import java.util.List; import java.util.Map; import java.util.regex.Pattern; import org.apache.commons.io.IOUtils; +import org.codehaus.plexus.util.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -79,11 +80,14 @@ public abstract class Commons { } public static String getEntityURI(final String entitySetName, final String entityKey) { - return entitySetName + "(" + entityKey + ")"; + // expected singleton in case of null key + return entitySetName + (StringUtils.isNotBlank(entityKey) ? "(" + entityKey + ")" : ""); } public static String getEntityBasePath(final String entitySetName, final String entityKey) { - return entitySetName + File.separatorChar + getEntityKey(entityKey) + File.separatorChar; + // expected singleton in case of null key + return entitySetName + File.separatorChar + + (StringUtils.isNotBlank(entityKey) ? getEntityKey(entityKey) + File.separatorChar : ""); } public static String getLinksURI( @@ -266,8 +270,18 @@ public abstract class Commons { public static Map.Entry parseEntityURI(final String uri) { final String relPath = uri.substring(uri.lastIndexOf("/")); final int branchIndex = relPath.indexOf('('); - final String es = relPath.substring(0, branchIndex); - final String eid = relPath.substring(branchIndex + 1, relPath.indexOf(')')); + + final String es; + final String eid; + + if (branchIndex > -1) { + es = relPath.substring(0, branchIndex); + eid = relPath.substring(branchIndex + 1, relPath.indexOf(')')); + } else { + es = relPath; + eid = null; + } + return new SimpleEntry(es, eid); } } diff --git a/fit/src/main/java/org/apache/olingo/fit/utils/Constants.java b/fit/src/main/java/org/apache/olingo/fit/utils/Constants.java index 72309c226..be522d967 100644 --- a/fit/src/main/java/org/apache/olingo/fit/utils/Constants.java +++ b/fit/src/main/java/org/apache/olingo/fit/utils/Constants.java @@ -60,9 +60,13 @@ public class Constants { public final static String LINK = "link"; - public final static String DATASERVICES_NS = "http://schemas.microsoft.com/ado/2007/08/dataservices"; + public final static String V3_DATASERVICES_NS = "http://schemas.microsoft.com/ado/2007/08/dataservices"; - public final static String METADATA_NS = "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"; + public final static String V4_DATASERVICES_NS = "http://docs.oasis-open.org/odata/ns/dataservices"; + + public final static String V3_METADATA_NS = "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"; + + public final static String V4_METADATA_NS = "http://docs.oasis-open.org/odata/ns/metadata"; public final static String METADATA = "metadata"; @@ -72,6 +76,8 @@ public class Constants { public final static String ENTITY = "entity"; + public final static String REF = "references"; + public final static String MEDIA_CONTENT_FILENAME = "$value.bin"; public final static String SKIP_TOKEN = "skiptoken"; diff --git a/fit/src/main/java/org/apache/olingo/fit/utils/MetadataLinkInfo.java b/fit/src/main/java/org/apache/olingo/fit/utils/MetadataLinkInfo.java index 737fa7bdf..f18b8b818 100644 --- a/fit/src/main/java/org/apache/olingo/fit/utils/MetadataLinkInfo.java +++ b/fit/src/main/java/org/apache/olingo/fit/utils/MetadataLinkInfo.java @@ -28,6 +28,14 @@ public class MetadataLinkInfo { private Map entitySets = new HashMap(); + public void setSingleton(final String entitySetName) { + entitySets.get(entitySetName).setSingleton(true); + } + + public boolean isSingleton(final String entitySetName) { + return entitySets.get(entitySetName).isSingleton(); + } + public Set getEntitySets() { return entitySets.keySet(); } @@ -92,6 +100,8 @@ public class MetadataLinkInfo { private Set links; + private boolean singleton; + public EntitySet(final String name) { this.name = name; links = new HashSet(); @@ -135,6 +145,18 @@ public class MetadataLinkInfo { this.links = links; } + public EntitySet(boolean singleton) { + this.singleton = singleton; + } + + public boolean isSingleton() { + return singleton; + } + + public void setSingleton(boolean singleton) { + this.singleton = singleton; + } + @Override public String toString() { return name + ": " + links; diff --git a/fit/src/main/java/org/apache/olingo/fit/utils/v4/XMLUtilities.java b/fit/src/main/java/org/apache/olingo/fit/utils/v4/XMLUtilities.java index c61d272b8..1cf8d38db 100644 --- a/fit/src/main/java/org/apache/olingo/fit/utils/v4/XMLUtilities.java +++ b/fit/src/main/java/org/apache/olingo/fit/utils/v4/XMLUtilities.java @@ -138,5 +138,9 @@ public class XMLUtilities extends org.apache.olingo.fit.utils.AbstractXMLUtiliti if (size == 0) { metadataLinkInfo.addEntitySet(entitySetName); } + + if (singletons.contains(entitySetName)) { + metadataLinkInfo.setSingleton(entitySetName); + } } } diff --git a/fit/src/main/resources/v4/Company/entity.full.json b/fit/src/main/resources/v4/Company/entity.full.json new file mode 100644 index 000000000..41d4ffd2a --- /dev/null +++ b/fit/src/main/resources/v4/Company/entity.full.json @@ -0,0 +1,38 @@ +{ + "@odata.context": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/$metadata#Company", + "@odata.type": "#Microsoft.Test.OData.Services.ODataWCFService.Company", + "@odata.id": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Company", + "@odata.editLink": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Company", + "CompanyID": 0, + "CompanyCategory@odata.type": "#Microsoft.Test.OData.Services.ODataWCFService.CompanyCategory", + "CompanyCategory": "IT", + "Revenue@odata.type": "#Int64", + "Revenue": 100000, + "Name": "MS", + "Address": + { + "@odata.type": "#Microsoft.Test.OData.Services.ODataWCFService.CompanyAddress", + "Street": "1 Microsoft Way", + "City": "Redmond", + "PostalCode": "98052", + "CompanyName": "Microsoft" + }, + "Employees@odata.associationLink": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Company/Employees/$ref", + "Employees@odata.navigationLink": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Company/Employees", + "VipCustomer@odata.associationLink": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Company/VipCustomer/$ref", + "VipCustomer@odata.navigationLink": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Company/VipCustomer", + "Departments@odata.associationLink": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Company/Departments/$ref", + "Departments@odata.navigationLink": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Company/Departments", + "CoreDepartment@odata.associationLink": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Company/CoreDepartment/$ref", + "CoreDepartment@odata.navigationLink": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Company/CoreDepartment", + "#Microsoft.Test.OData.Services.ODataWCFService.IncreaseRevenue": + { + "title": "Microsoft.Test.OData.Services.ODataWCFService.IncreaseRevenue", + "target": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Company/Microsoft.Test.OData.Services.ODataWCFService.IncreaseRevenue" + }, + "#Microsoft.Test.OData.Services.ODataWCFService.GetEmployeesCount": + { + "title": "Microsoft.Test.OData.Services.ODataWCFService.GetEmployeesCount", + "target": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Company/Microsoft.Test.OData.Services.ODataWCFService.GetEmployeesCount" + } +} \ No newline at end of file diff --git a/fit/src/main/resources/v4/Company/entity.xml b/fit/src/main/resources/v4/Company/entity.xml new file mode 100644 index 000000000..6beed3b65 --- /dev/null +++ b/fit/src/main/resources/v4/Company/entity.xml @@ -0,0 +1,49 @@ + + + + http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Company + + + + + + + + <updated>2014-03-24T17:09:31Z</updated> + <author> + <name /> + </author> + <content type="application/xml"> + <m:properties> + <d:CompanyID m:type="Int32">0</d:CompanyID> + <d:CompanyCategory m:type="#Microsoft.Test.OData.Services.ODataWCFService.CompanyCategory">IT</d:CompanyCategory> + <d:Revenue m:type="Int64">100000</d:Revenue> + <d:Name>MS</d:Name> + <d:Address m:type="#Microsoft.Test.OData.Services.ODataWCFService.CompanyAddress"> + <d:Street>1 Microsoft Way</d:Street> + <d:City>Redmond</d:City> + <d:PostalCode>98052</d:PostalCode> + <d:CompanyName>Microsoft</d:CompanyName> + </d:Address> + </m:properties> + </content> +</entry> \ No newline at end of file diff --git a/fit/src/main/resources/v4/Customers/1/entity.full.json b/fit/src/main/resources/v4/Customers/1/entity.full.json new file mode 100644 index 000000000..77ce0558c --- /dev/null +++ b/fit/src/main/resources/v4/Customers/1/entity.full.json @@ -0,0 +1,67 @@ +{ + "@odata.context": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/$metadata#Customers/$entity", + "@odata.type": "#Microsoft.Test.OData.Services.ODataWCFService.Customer", + "@odata.id": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Customers(PersonID=1)", + "@odata.editLink": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Customers(PersonID=1)", + "PersonID": 1, + "FirstName": "Bob", + "LastName": "Cat", + "MiddleName": null, + "HomeAddress": + { + "@odata.type": "#Microsoft.Test.OData.Services.ODataWCFService.HomeAddress", + "Street": "1 Microsoft Way", + "City": "London", + "PostalCode": "98052", + "FamilyName": "Cats" + }, + "Home@odata.type": "#GeographyPoint", + "Home": + { + "type": "Point", + "coordinates": + [ + 23.1, + 32.1 + ], + "crs": + { + "type": "name", + "properties": + { + "name": "EPSG:4326" + } + } + }, + "Numbers@odata.type": "#Collection(String)", + "Numbers": + [ + "111-111-1111" + ], + "Emails@odata.type": "#Collection(String)", + "Emails": + [ + "abc@abc.com" + ], + "City": "London", + "Birthday@odata.type": "#DateTimeOffset", + "Birthday": "1957-04-03T00:00:00Z", + "TimeBetweenLastTwoOrders@odata.type": "#Duration", + "TimeBetweenLastTwoOrders": "PT0.0000001S", + "Parent@odata.associationLink": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Customers(PersonID=1)/Parent/$ref", + "Parent@odata.navigationLink": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Customers(PersonID=1)/Parent", + "Orders@odata.associationLink": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Customers(PersonID=1)/Orders/$ref", + "Orders@odata.navigationLink": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Customers(PersonID=1)/Orders", + "Company@odata.associationLink": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Customers(PersonID=1)/Company/$ref", + "Company@odata.navigationLink": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Customers(PersonID=1)/Company", + "#Microsoft.Test.OData.Services.ODataWCFService.ResetAddress": + { + "title": "Microsoft.Test.OData.Services.ODataWCFService.ResetAddress", + "target": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Customers(PersonID=1)/Microsoft.Test.OData.Services.ODataWCFService.ResetAddress" + }, + "#Microsoft.Test.OData.Services.ODataWCFService.GetHomeAddress": + { + "title": "Microsoft.Test.OData.Services.ODataWCFService.GetHomeAddress", + "target": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Customers(PersonID=1)/Microsoft.Test.OData.Services.ODataWCFService.GetHomeAddress" + } +} \ No newline at end of file diff --git a/fit/src/main/resources/v4/Customers/1/entity.xml b/fit/src/main/resources/v4/Customers/1/entity.xml new file mode 100644 index 000000000..d3e25a69a --- /dev/null +++ b/fit/src/main/resources/v4/Customers/1/entity.xml @@ -0,0 +1,67 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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. + +--> +<entry xml:base="http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/" + xmlns="http://www.w3.org/2005/Atom" xmlns:d="http://docs.oasis-open.org/odata/ns/data" + xmlns:m="http://docs.oasis-open.org/odata/ns/metadata" + xmlns:georss="http://www.georss.org/georss" + xmlns:gml="http://www.opengis.net/gml" + m:context="http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/$metadata#Customers/$entity"> + <id>http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Customers(PersonID=1)</id> + <category term="#Microsoft.Test.OData.Services.ODataWCFService.Customer" scheme="http://docs.oasis-open.org/odata/ns/scheme" /> + <link rel="edit" href="http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Customers(PersonID=1)" /> + <link rel="http://docs.oasis-open.org/odata/ns/related/Parent" type="application/atom+xml;type=entry" title="Parent" href="http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Customers(PersonID=1)/Parent" /> + <link rel="http://docs.oasis-open.org/odata/ns/related/Orders" type="application/atom+xml;type=feed" title="Orders" href="http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Customers(PersonID=1)/Orders" /> + <link rel="http://docs.oasis-open.org/odata/ns/related/Company" type="application/atom+xml;type=entry" title="Company" href="http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Customers(PersonID=1)/Company" /> + <title /> + <updated>2014-03-24T17:03:20Z</updated> + <author> + <name /> + </author> + <content type="application/xml"> + <m:properties> + <d:PersonID m:type="Int32">1</d:PersonID> + <d:FirstName>Bob</d:FirstName> + <d:LastName>Cat</d:LastName> + <d:MiddleName m:null="true" /> + <d:HomeAddress m:type="#Microsoft.Test.OData.Services.ODataWCFService.HomeAddress"> + <d:Street>1 Microsoft Way</d:Street> + <d:City>London</d:City> + <d:PostalCode>98052</d:PostalCode> + <d:FamilyName>Cats</d:FamilyName> + </d:HomeAddress> + <d:Home m:type="GeographyPoint"> + <gml:Point gml:srsName="http://www.opengis.net/def/crs/EPSG/0/4326"> + <gml:pos>32.1 23.1</gml:pos> + </gml:Point> + </d:Home> + <d:Numbers m:type="#Collection(String)"> + <m:element>111-111-1111</m:element> + </d:Numbers> + <d:Emails m:type="#Collection(String)"> + <m:element>abc@abc.com</m:element> + </d:Emails> + <d:City>London</d:City> + <d:Birthday m:type="DateTimeOffset">1957-04-03T00:00:00Z</d:Birthday> + <d:TimeBetweenLastTwoOrders m:type="Duration">PT0.0000001S</d:TimeBetweenLastTwoOrders> + </m:properties> + </content> +</entry> \ No newline at end of file diff --git a/fit/src/main/resources/v4/Customers/1/links/Company.full.json b/fit/src/main/resources/v4/Customers/1/links/Company.full.json new file mode 100644 index 000000000..3d307c84e --- /dev/null +++ b/fit/src/main/resources/v4/Customers/1/links/Company.full.json @@ -0,0 +1,4 @@ +{ + "odata.metadata": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/$metadata#Customer/$links/Info", + "url": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Company" +} \ No newline at end of file diff --git a/fit/src/main/resources/v4/Customers/1/links/Company.xml b/fit/src/main/resources/v4/Customers/1/links/Company.xml new file mode 100644 index 000000000..1bf957591 --- /dev/null +++ b/fit/src/main/resources/v4/Customers/1/links/Company.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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. + +--> +<uri xmlns="http://schemas.microsoft.com/ado/2007/08/dataservices">http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Company</uri> \ No newline at end of file diff --git a/fit/src/main/resources/v4/Customers/1/links/Orders.full.json b/fit/src/main/resources/v4/Customers/1/links/Orders.full.json new file mode 100644 index 000000000..d684d9a51 --- /dev/null +++ b/fit/src/main/resources/v4/Customers/1/links/Orders.full.json @@ -0,0 +1,10 @@ +{ + "odata.metadata": "http://localhost:${cargo.servlet.port}/StaticService/V30/Static.svc/$metadata#Customer/$links/Orders", + "value": + [ + { + "url": "http://localhost:${cargo.servlet.port}/StaticService/V30/Static.svc/Orders(8)" + } + ], + "odata.nextLink": "http://localhost:${cargo.servlet.port}/StaticService/V30/Static.svc/Customers(1)/$links/Orders?$skiptoken=2" +} \ No newline at end of file diff --git a/fit/src/main/resources/v4/Customers/1/links/Orders.xml b/fit/src/main/resources/v4/Customers/1/links/Orders.xml new file mode 100644 index 000000000..52bd53def --- /dev/null +++ b/fit/src/main/resources/v4/Customers/1/links/Orders.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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. + +--> +<links xmlns="http://schemas.microsoft.com/ado/2007/08/dataservices"> + <uri>http://localhost:${cargo.servlet.port}/StaticService/V30/Static.svc/Orders(8)</uri> + <next>http://localhost:${cargo.servlet.port}/StaticService/V30/Static.svc/Customers(1)/$links/Orders?$skiptoken=2</next> +</links> \ No newline at end of file diff --git a/fit/src/main/resources/v4/Orders/8/entity.full.json b/fit/src/main/resources/v4/Orders/8/entity.full.json new file mode 100644 index 000000000..69199f2bc --- /dev/null +++ b/fit/src/main/resources/v4/Orders/8/entity.full.json @@ -0,0 +1,23 @@ +{ + "@odata.context": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/$metadata#Orders/$entity", + "@odata.type": "#Microsoft.Test.OData.Services.ODataWCFService.Order", + "@odata.id": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Orders(8)", + "@odata.editLink": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Orders(8)", + "@odata.etag": "W/\"123456789001\"", + "OrderID": 8, + "OrderDate@odata.type": "#DateTimeOffset", + "OrderDate": "2011-03-04T16:03:57Z", + "ShelfLife@odata.type": "#Duration", + "ShelfLife": "PT0.0000001S", + "OrderShelfLifes@odata.type": "#Collection(Duration)", + "OrderShelfLifes": + [ + "PT0.0000001S" + ], + "LoggedInEmployee@odata.associationLink": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Orders(8)/LoggedInEmployee/$ref", + "LoggedInEmployee@odata.navigationLink": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Orders(8)/LoggedInEmployee", + "CustomerForOrder@odata.associationLink": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Orders(8)/CustomerForOrder/$ref", + "CustomerForOrder@odata.navigationLink": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Orders(8)/CustomerForOrder", + "OrderDetails@odata.associationLink": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Orders(8)/OrderDetails/$ref", + "OrderDetails@odata.navigationLink": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Orders(8)/OrderDetails" +} \ No newline at end of file diff --git a/fit/src/main/resources/v4/Orders/8/entity.xml b/fit/src/main/resources/v4/Orders/8/entity.xml new file mode 100644 index 000000000..8dec66dda --- /dev/null +++ b/fit/src/main/resources/v4/Orders/8/entity.xml @@ -0,0 +1,50 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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. + +--> +<entry xml:base="http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/" + xmlns="http://www.w3.org/2005/Atom" xmlns:d="http://docs.oasis-open.org/odata/ns/data" + xmlns:m="http://docs.oasis-open.org/odata/ns/metadata" + xmlns:georss="http://www.georss.org/georss" + xmlns:gml="http://www.opengis.net/gml" + m:context="http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/$metadata#Orders/$entity" + m:etag="W/"123456789001""> + <id>http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Orders(8)</id> + <category term="#Microsoft.Test.OData.Services.ODataWCFService.Order" scheme="http://docs.oasis-open.org/odata/ns/scheme" /> + <link rel="edit" href="http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Orders(8)" /> + <link rel="http://docs.oasis-open.org/odata/ns/related/LoggedInEmployee" type="application/atom+xml;type=entry" title="LoggedInEmployee" href="http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Orders(8)/LoggedInEmployee" /> + <link rel="http://docs.oasis-open.org/odata/ns/related/CustomerForOrder" type="application/atom+xml;type=entry" title="CustomerForOrder" href="http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Orders(8)/CustomerForOrder" /> + <link rel="http://docs.oasis-open.org/odata/ns/related/OrderDetails" type="application/atom+xml;type=feed" title="OrderDetails" href="http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Orders(8)/OrderDetails" /> + <title /> + <updated>2014-03-24T17:36:01Z</updated> + <author> + <name /> + </author> + <content type="application/xml"> + <m:properties> + <d:OrderID m:type="Int32">8</d:OrderID> + <d:OrderDate m:type="DateTimeOffset">2011-03-04T16:03:57Z</d:OrderDate> + <d:ShelfLife m:type="Duration">PT0.0000001S</d:ShelfLife> + <d:OrderShelfLifes m:type="#Collection(Duration)"> + <m:element>PT0.0000001S</m:element> + </d:OrderShelfLifes> + </m:properties> + </content> +</entry> \ No newline at end of file diff --git a/fit/src/main/resources/v4/Orders/8/etag.txt b/fit/src/main/resources/v4/Orders/8/etag.txt new file mode 100644 index 000000000..46cda3159 --- /dev/null +++ b/fit/src/main/resources/v4/Orders/8/etag.txt @@ -0,0 +1 @@ +W/"123456789001" \ No newline at end of file diff --git a/fit/src/main/resources/v4/Orders/8/links/.full.json b/fit/src/main/resources/v4/Orders/8/links/.full.json new file mode 100644 index 000000000..ae6a7fd70 --- /dev/null +++ b/fit/src/main/resources/v4/Orders/8/links/.full.json @@ -0,0 +1,4 @@ +{ + "odata.metadata": "http://localhost:${cargo.servlet.port}/StaticService/V30/Static.svc/$metadata#Customer/$links/Info", + "url": "http://localhost:${cargo.servlet.port}/StaticService/V30/Static.svc/CustomerInfo(11)" +} \ No newline at end of file diff --git a/fit/src/main/resources/v4/Orders/8/links/CustomerForOrder.full.json b/fit/src/main/resources/v4/Orders/8/links/CustomerForOrder.full.json new file mode 100644 index 000000000..3a57fd094 --- /dev/null +++ b/fit/src/main/resources/v4/Orders/8/links/CustomerForOrder.full.json @@ -0,0 +1,4 @@ +{ + "odata.metadata": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/$metadata#Orders/$links/CustomerForOrder", + "url": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Customers(1)" +} \ No newline at end of file diff --git a/fit/src/main/resources/v4/Orders/8/links/CustomerForOrder.xml b/fit/src/main/resources/v4/Orders/8/links/CustomerForOrder.xml new file mode 100644 index 000000000..f7e70fcba --- /dev/null +++ b/fit/src/main/resources/v4/Orders/8/links/CustomerForOrder.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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. + +--> +<uri xmlns="http://schemas.microsoft.com/ado/2007/08/dataservices">http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Customers(1)</uri> \ No newline at end of file diff --git a/fit/src/main/resources/v4/Orders/feed.full.json b/fit/src/main/resources/v4/Orders/feed.full.json new file mode 100644 index 000000000..1752c62f1 --- /dev/null +++ b/fit/src/main/resources/v4/Orders/feed.full.json @@ -0,0 +1,48 @@ +{ + "@odata.context": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/$metadata#Orders", + "value": + [ + { + "@odata.type": "#Microsoft.Test.OData.Services.ODataWCFService.Order", + "@odata.id": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Orders(7)", + "@odata.editLink": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Orders(7)", + "OrderID": 7, + "OrderDate@odata.type": "#DateTimeOffset", + "OrderDate": "2011-05-29T14:21:12Z", + "ShelfLife@odata.type": "#Duration", + "ShelfLife": "PT0.0000001S", + "OrderShelfLifes@odata.type": "#Collection(Duration)", + "OrderShelfLifes": + [ + "PT0.0000001S" + ], + "LoggedInEmployee@odata.associationLink": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Orders(7)/LoggedInEmployee/$ref", + "LoggedInEmployee@odata.navigationLink": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Orders(7)/LoggedInEmployee", + "CustomerForOrder@odata.associationLink": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Orders(7)/CustomerForOrder/$ref", + "CustomerForOrder@odata.navigationLink": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Orders(7)/CustomerForOrder", + "OrderDetails@odata.associationLink": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Orders(7)/OrderDetails/$ref", + "OrderDetails@odata.navigationLink": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Orders(7)/OrderDetails" + }, + { + "@odata.type": "#Microsoft.Test.OData.Services.ODataWCFService.Order", + "@odata.id": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Orders(8)", + "@odata.editLink": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Orders(8)", + "OrderID": 8, + "OrderDate@odata.type": "#DateTimeOffset", + "OrderDate": "2011-03-04T16:03:57Z", + "ShelfLife@odata.type": "#Duration", + "ShelfLife": "PT0.0000001S", + "OrderShelfLifes@odata.type": "#Collection(Duration)", + "OrderShelfLifes": + [ + "PT0.0000001S" + ], + "LoggedInEmployee@odata.associationLink": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Orders(8)/LoggedInEmployee/$ref", + "LoggedInEmployee@odata.navigationLink": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Orders(8)/LoggedInEmployee", + "CustomerForOrder@odata.associationLink": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Orders(8)/CustomerForOrder/$ref", + "CustomerForOrder@odata.navigationLink": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Orders(8)/CustomerForOrder", + "OrderDetails@odata.associationLink": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Orders(8)/OrderDetails/$ref", + "OrderDetails@odata.navigationLink": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Orders(8)/OrderDetails" + } + ] +} \ No newline at end of file diff --git a/fit/src/main/resources/v4/Orders/feed.xml b/fit/src/main/resources/v4/Orders/feed.xml new file mode 100644 index 000000000..2a32d3172 --- /dev/null +++ b/fit/src/main/resources/v4/Orders/feed.xml @@ -0,0 +1,72 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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. + +--> +<feed xml:base="http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/" xmlns="http://www.w3.org/2005/Atom" xmlns:d="http://docs.oasis-open.org/odata/ns/data" xmlns:m="http://docs.oasis-open.org/odata/ns/metadata" xmlns:georss="http://www.georss.org/georss" xmlns:gml="http://www.opengis.net/gml" m:context="http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/$metadata#Orders"> + <id>http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Orders</id> + <title /> + <updated>2014-03-24T17:17:25Z</updated> + <entry> + <id>http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Orders(7)</id> + <category term="#Microsoft.Test.OData.Services.ODataWCFService.Order" scheme="http://docs.oasis-open.org/odata/ns/scheme" /> + <link rel="edit" href="http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Orders(7)" /> + <link rel="http://docs.oasis-open.org/odata/ns/related/LoggedInEmployee" type="application/atom+xml;type=entry" title="LoggedInEmployee" href="http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Orders(7)/LoggedInEmployee" /> + <link rel="http://docs.oasis-open.org/odata/ns/related/CustomerForOrder" type="application/atom+xml;type=entry" title="CustomerForOrder" href="http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Orders(7)/CustomerForOrder" /> + <link rel="http://docs.oasis-open.org/odata/ns/related/OrderDetails" type="application/atom+xml;type=feed" title="OrderDetails" href="http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Orders(7)/OrderDetails" /> + <title /> + <updated>2014-03-24T17:17:25Z</updated> + <author> + <name /> + </author> + <content type="application/xml"> + <m:properties> + <d:OrderID m:type="Int32">7</d:OrderID> + <d:OrderDate m:type="DateTimeOffset">2011-05-29T14:21:12Z</d:OrderDate> + <d:ShelfLife m:type="Duration">PT0.0000001S</d:ShelfLife> + <d:OrderShelfLifes m:type="#Collection(Duration)"> + <m:element>PT0.0000001S</m:element> + </d:OrderShelfLifes> + </m:properties> + </content> + </entry> + <entry> + <id>http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Orders(8)</id> + <category term="#Microsoft.Test.OData.Services.ODataWCFService.Order" scheme="http://docs.oasis-open.org/odata/ns/scheme" /> + <link rel="edit" href="http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Orders(8)" /> + <link rel="http://docs.oasis-open.org/odata/ns/related/LoggedInEmployee" type="application/atom+xml;type=entry" title="LoggedInEmployee" href="http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Orders(8)/LoggedInEmployee" /> + <link rel="http://docs.oasis-open.org/odata/ns/related/CustomerForOrder" type="application/atom+xml;type=entry" title="CustomerForOrder" href="http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Orders(8)/CustomerForOrder" /> + <link rel="http://docs.oasis-open.org/odata/ns/related/OrderDetails" type="application/atom+xml;type=feed" title="OrderDetails" href="http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Orders(8)/OrderDetails" /> + <title /> + <updated>2014-03-24T17:17:25Z</updated> + <author> + <name /> + </author> + <content type="application/xml"> + <m:properties> + <d:OrderID m:type="Int32">8</d:OrderID> + <d:OrderDate m:type="DateTimeOffset">2011-03-04T16:03:57Z</d:OrderDate> + <d:ShelfLife m:type="Duration">PT0.0000001S</d:ShelfLife> + <d:OrderShelfLifes m:type="#Collection(Duration)"> + <m:element>PT0.0000001S</m:element> + </d:OrderShelfLifes> + </m:properties> + </content> + </entry> +</feed> \ No newline at end of file diff --git a/fit/src/main/resources/v4/People/5/entity.full.json b/fit/src/main/resources/v4/People/5/entity.full.json new file mode 100644 index 000000000..35968810f --- /dev/null +++ b/fit/src/main/resources/v4/People/5/entity.full.json @@ -0,0 +1,51 @@ +{ + "@odata.context": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/$metadata#People/$entity", + "@odata.type": "#Microsoft.Test.OData.Services.ODataWCFService.Person", + "@odata.id": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/People(5)", + "@odata.editLink": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/People(5)", + "PersonID": 5, + "FirstName": "Peter", + "LastName": "Bee", + "MiddleName": null, + "HomeAddress": null, + "Home@odata.type": "#GeographyPoint", + "Home": + { + "type": "Point", + "coordinates": + [ + -261.8, + -16 + ], + "crs": + { + "type": "name", + "properties": + { + "name": "EPSG:4326" + } + } + }, + "Numbers@odata.type": "#Collection(String)", + "Numbers": + [ + "555-555-5555" + ], + "Emails@odata.type": "#Collection(String)", + "Emails": + [ + "def@test.msn" + ], + "Parent@odata.associationLink": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/People(5)/Parent/$ref", + "Parent@odata.navigationLink": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/People(5)/Parent", + "#Microsoft.Test.OData.Services.ODataWCFService.ResetAddress": + { + "title": "Microsoft.Test.OData.Services.ODataWCFService.ResetAddress", + "target": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/People(5)/Microsoft.Test.OData.Services.ODataWCFService.ResetAddress" + }, + "#Microsoft.Test.OData.Services.ODataWCFService.GetHomeAddress": + { + "title": "Microsoft.Test.OData.Services.ODataWCFService.GetHomeAddress", + "target": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/People(5)/Microsoft.Test.OData.Services.ODataWCFService.GetHomeAddress" + } +} \ No newline at end of file diff --git a/fit/src/main/resources/v4/People/5/entity.xml b/fit/src/main/resources/v4/People/5/entity.xml new file mode 100644 index 000000000..daebef80f --- /dev/null +++ b/fit/src/main/resources/v4/People/5/entity.xml @@ -0,0 +1,55 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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. + +--> +<entry xml:base="http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/" xmlns="http://www.w3.org/2005/Atom" xmlns:d="http://docs.oasis-open.org/odata/ns/data" xmlns:m="http://docs.oasis-open.org/odata/ns/metadata" xmlns:georss="http://www.georss.org/georss" xmlns:gml="http://www.opengis.net/gml" m:context="http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/$metadata#People/$entity"> + <id>http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/People(5)</id> + <category term="#Microsoft.Test.OData.Services.ODataWCFService.Person" scheme="http://docs.oasis-open.org/odata/ns/scheme" /> + <link rel="edit" href="http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/People(5)" /> + <link rel="http://docs.oasis-open.org/odata/ns/related/Parent" type="application/atom+xml;type=entry" title="Parent" href="http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/People(5)/Parent" /> + <title /> + <updated>2014-03-24T17:20:17Z</updated> + <author> + <name /> + </author> + <content type="application/xml"> + <m:properties> + <d:PersonID m:type="Int32">5</d:PersonID> + <d:FirstName>Peter</d:FirstName> + <d:LastName>Bee</d:LastName> + <d:MiddleName m:null="true" /> + <d:HomeAddress m:null="true" /> + <d:Home m:type="GeographyPoint"> + <gml:Point gml:srsName="http://www.opengis.net/def/crs/EPSG/0/4326"> + <gml:pos>-16 -261.8</gml:pos> + </gml:Point> + </d:Home> + <d:Numbers m:type="#Collection(String)"> + <m:element>555-555-5555</m:element> + </d:Numbers> + <d:Emails m:type="#Collection(String)"> + <m:element>def@test.msn</m:element> + </d:Emails> + <d:IsRegistered m:type="Boolean">true</d:IsRegistered> + <d:Height m:type="Decimal">179</d:Height> + <d:PDC m:type="Binary">fi653p3+MklA/LdoBlhWgnMTUUEo8tEgtbMXnF0a3CUNL9BZxXpSRiD9ebTnmNR0zWPjJVIDx4tdmCnq55XrJh+RW9aI/b34wAogK3kcORw=</d:PDC> + </m:properties> + </content> +</entry> \ No newline at end of file diff --git a/fit/src/main/resources/v4/ProductDetails/6 1/entity.full.json b/fit/src/main/resources/v4/ProductDetails/6 1/entity.full.json new file mode 100644 index 000000000..38e347f28 --- /dev/null +++ b/fit/src/main/resources/v4/ProductDetails/6 1/entity.full.json @@ -0,0 +1,19 @@ +{ + "@odata.context": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/$metadata#ProductDetails/$entity", + "@odata.type": "#Microsoft.Test.OData.Services.ODataWCFService.ProductDetail", + "@odata.id": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/ProductDetails(ProductID=6,ProductDetailID=1)", + "@odata.editLink": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/ProductDetails(ProductID=6,ProductDetailID=1)", + "ProductID": 6, + "ProductDetailID": 1, + "ProductName": "Candy", + "Description": "sweet snack", + "RelatedProduct@odata.associationLink": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/ProductDetails(ProductID=6,ProductDetailID=1)/RelatedProduct/$ref", + "RelatedProduct@odata.navigationLink": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/ProductDetails(ProductID=6,ProductDetailID=1)/RelatedProduct", + "Reviews@odata.associationLink": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/ProductDetails(ProductID=6,ProductDetailID=1)/Reviews/$ref", + "Reviews@odata.navigationLink": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/ProductDetails(ProductID=6,ProductDetailID=1)/Reviews", + "#Microsoft.Test.OData.Services.ODataWCFService.GetRelatedProduct": + { + "title": "Microsoft.Test.OData.Services.ODataWCFService.GetRelatedProduct", + "target": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/ProductDetails(ProductID=6,ProductDetailID=1)/Microsoft.Test.OData.Services.ODataWCFService.GetRelatedProduct" + } +} \ No newline at end of file diff --git a/fit/src/main/resources/v4/ProductDetails/6 1/entity.xml b/fit/src/main/resources/v4/ProductDetails/6 1/entity.xml new file mode 100644 index 000000000..ae66ac858 --- /dev/null +++ b/fit/src/main/resources/v4/ProductDetails/6 1/entity.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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. + +--> +<entry xml:base="http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/" xmlns="http://www.w3.org/2005/Atom" xmlns:d="http://docs.oasis-open.org/odata/ns/data" xmlns:m="http://docs.oasis-open.org/odata/ns/metadata" xmlns:georss="http://www.georss.org/georss" xmlns:gml="http://www.opengis.net/gml" m:context="http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/$metadata#ProductDetails/$entity"> + <id>http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/ProductDetails(ProductID=6,ProductDetailID=1)</id> + <category term="#Microsoft.Test.OData.Services.ODataWCFService.ProductDetail" scheme="http://docs.oasis-open.org/odata/ns/scheme" /> + <link rel="edit" href="http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/ProductDetails(ProductID=6,ProductDetailID=1)" /> + <link rel="http://docs.oasis-open.org/odata/ns/related/RelatedProduct" type="application/atom+xml;type=entry" title="RelatedProduct" href="http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/ProductDetails(ProductID=6,ProductDetailID=1)/RelatedProduct" /> + <link rel="http://docs.oasis-open.org/odata/ns/related/Reviews" type="application/atom+xml;type=feed" title="Reviews" href="http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/ProductDetails(ProductID=6,ProductDetailID=1)/Reviews" /> + <title /> + <updated>2014-03-24T17:23:51Z</updated> + <author> + <name /> + </author> + <content type="application/xml"> + <m:properties> + <d:ProductID m:type="Int32">6</d:ProductID> + <d:ProductDetailID m:type="Int32">1</d:ProductDetailID> + <d:ProductName>Candy</d:ProductName> + <d:Description>sweet snack</d:Description> + </m:properties> + </content> +</entry> \ No newline at end of file diff --git a/fit/src/main/resources/v4/badRequest.json b/fit/src/main/resources/v4/badRequest.json new file mode 100644 index 000000000..b6bc3ac9c --- /dev/null +++ b/fit/src/main/resources/v4/badRequest.json @@ -0,0 +1,17 @@ +{ + "odata.error": + { + "code": "", + "message": + { + "lang": "en-US", + "value": "Bad request." + }, + "innererror": + { + "message": "Bad request.", + "type": "Microsoft.Data.OData.BadRequest", + "stacktrace": " at Microsoft.Data.OData.MediaTypeUtils.GetContentTypeFromSettings...." + } + } +} \ No newline at end of file diff --git a/fit/src/main/resources/v4/badRequest.xml b/fit/src/main/resources/v4/badRequest.xml new file mode 100644 index 000000000..febd2febc --- /dev/null +++ b/fit/src/main/resources/v4/badRequest.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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. + +--> +<m:error xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"> + <m:code /> + <m:message xml:lang="en-US">Bad request.</m:message> + <m:innererror> + <m:message>Bad request.</m:message> + <m:type>Microsoft.Data.OData.BadRequest</m:type> + <m:stacktrace> at Microsoft.Data.OData.MediaTypeUtils.GetContentTypeFromSettings...</m:stacktrace> + </m:innererror> +</m:error> \ No newline at end of file diff --git a/fit/src/main/resources/v4/notFound.json b/fit/src/main/resources/v4/notFound.json new file mode 100644 index 000000000..fba2ac481 --- /dev/null +++ b/fit/src/main/resources/v4/notFound.json @@ -0,0 +1,11 @@ +{ + "odata.error": + { + "code": "", + "message": + { + "lang": "en-US", + "value": "Resource not found for the segment 'Customer'." + } + } +} \ No newline at end of file diff --git a/fit/src/main/resources/v4/notFound.xml b/fit/src/main/resources/v4/notFound.xml new file mode 100644 index 000000000..189d9adeb --- /dev/null +++ b/fit/src/main/resources/v4/notFound.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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. + +--> +<m:error xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"> + <m:code /> + <m:message xml:lang="en-US">Resource not found for the segment 'Customer'.</m:message> +</m:error> \ No newline at end of file diff --git a/fit/src/main/resources/v4/references/T3JkZXJzKDgpL0N1c3RvbWVyRm9yT3JkZXI=.full.json b/fit/src/main/resources/v4/references/T3JkZXJzKDgpL0N1c3RvbWVyRm9yT3JkZXI=.full.json new file mode 100644 index 000000000..e23e70c9c --- /dev/null +++ b/fit/src/main/resources/v4/references/T3JkZXJzKDgpL0N1c3RvbWVyRm9yT3JkZXI=.full.json @@ -0,0 +1,4 @@ +{ + "@odata.context": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/$metadata#$ref", + "@odata.id": "http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Customers(PersonID=1)" +} \ No newline at end of file diff --git a/fit/src/main/resources/v4/references/T3JkZXJzKDgpL0N1c3RvbWVyRm9yT3JkZXI=.xml b/fit/src/main/resources/v4/references/T3JkZXJzKDgpL0N1c3RvbWVyRm9yT3JkZXI=.xml new file mode 100644 index 000000000..6d847fcd9 --- /dev/null +++ b/fit/src/main/resources/v4/references/T3JkZXJzKDgpL0N1c3RvbWVyRm9yT3JkZXI=.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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. + +--> +<m:ref m:context="http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/$metadata#$ref" id="http://localhost:${cargo.servlet.port}/StaticService/V40/Static.svc/Customers(PersonID=1)" xmlns:m="http://docs.oasis-open.org/odata/ns/metadata" /> \ No newline at end of file diff --git a/fit/src/main/resources/v4/unsupportedMediaType.json b/fit/src/main/resources/v4/unsupportedMediaType.json new file mode 100644 index 000000000..4dc5a1f0f --- /dev/null +++ b/fit/src/main/resources/v4/unsupportedMediaType.json @@ -0,0 +1,17 @@ +{ + "odata.error": + { + "code": "", + "message": + { + "lang": "en-US", + "value": "Unsupported media type requested." + }, + "innererror": + { + "message": "Unsupported media type requested.", + "type": "Microsoft.Data.OData.ODataContentTypeException", + "stacktrace": " at Microsoft.Data.OData.MediaTypeUtils.GetContentTypeFromSettings(ODataMessageWriterSettings settings, ODataPayloadKind payloadKind, MediaTypeResolver mediaTypeResolver, MediaType& mediaType, Encoding& encoding)\n at Microsoft.Data.OData.ODataMessageWriter.EnsureODataFormatAndContentType()\n at Microsoft.Data.OData.ODataMessageWriter.SetHeaders(ODataPayloadKind payloadKind)\n at Microsoft.Data.OData.ODataUtils.SetHeadersForPayload(ODataMessageWriter messageWriter, ODataPayloadKind payloadKind)\n at System.Data.Services.ResponseContentTypeNegotiator.DetermineResponseFormat(ODataPayloadKind payloadKind, String acceptableMediaTypes, String acceptableCharSets)" + } + } +} \ No newline at end of file diff --git a/fit/src/main/resources/v4/unsupportedMediaType.xml b/fit/src/main/resources/v4/unsupportedMediaType.xml new file mode 100644 index 000000000..c157fb186 --- /dev/null +++ b/fit/src/main/resources/v4/unsupportedMediaType.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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. + +--> +<m:error xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"> + <m:code /> + <m:message xml:lang="en-US">Unsupported media type requested.</m:message> + <m:innererror> + <m:message>A supported MIME type could not be found that matches the acceptable MIME types for the request. The supported type(s) 'application/atom+xml;type=feed, application/atom+xml, application/json;odata=minimalmetadata;streaming=true, application/json;odata=minimalmetadata;streaming=false, application/json;odata=minimalmetadata, application/json;odata=fullmetadata;streaming=true, application/json;odata=fullmetadata;streaming=false, application/json;odata=fullmetadata, application/json;odata=nometadata;streaming=true, application/json;odata=nometadata;streaming=false, application/json;odata=nometadata, application/json;streaming=true, application/json;streaming=false, application/json;odata=verbose, application/json' do not match any of the acceptable MIME types 'application/xml'.</m:message> + <m:type>Microsoft.Data.OData.ODataContentTypeException</m:type> + <m:stacktrace> at Microsoft.Data.OData.MediaTypeUtils.GetContentTypeFromSettings(ODataMessageWriterSettings settings, ODataPayloadKind payloadKind, MediaTypeResolver mediaTypeResolver, MediaType& mediaType, Encoding& encoding) + at Microsoft.Data.OData.ODataMessageWriter.EnsureODataFormatAndContentType() + at Microsoft.Data.OData.ODataMessageWriter.SetHeaders(ODataPayloadKind payloadKind) + at Microsoft.Data.OData.ODataUtils.SetHeadersForPayload(ODataMessageWriter messageWriter, ODataPayloadKind payloadKind) + at System.Data.Services.ResponseContentTypeNegotiator.DetermineResponseFormat(ODataPayloadKind payloadKind, String acceptableMediaTypes, String acceptableCharSets)</m:stacktrace> + </m:innererror> +</m:error> \ No newline at end of file diff --git a/lib/client-api/src/main/java/org/apache/olingo/client/api/communication/header/HeaderName.java b/lib/client-api/src/main/java/org/apache/olingo/client/api/communication/header/HeaderName.java index b68cf3db8..67b3043f7 100644 --- a/lib/client-api/src/main/java/org/apache/olingo/client/api/communication/header/HeaderName.java +++ b/lib/client-api/src/main/java/org/apache/olingo/client/api/communication/header/HeaderName.java @@ -18,6 +18,11 @@ */ package org.apache.olingo.client.api.communication.header; +import java.util.Arrays; +import java.util.List; +import org.apache.olingo.commons.api.ODataRuntimeException; +import org.apache.olingo.commons.api.edm.constants.ODataServiceVersion; + /** * Major OData request/response header names. */ @@ -26,7 +31,17 @@ public enum HeaderName { /** * The OData protocol uses the Accept request-header field, as specified in [RFC2616]. */ - accept("Accept"), + accept("Accept", Arrays.asList(ODataServiceVersion.V30, ODataServiceVersion.V40)), + /** + * As specified in [RFC2616], the client MAY specify the set of accepted character sets with the Accept-Charset + * header. + */ + acceptCharset("Accept-Charset", Arrays.asList(ODataServiceVersion.V40)), + /** + * As specified in [RFC2616], the client MAY specify the set of accepted natural languages with the Accept-Language + * header. + */ + acceptLanguage("Accept-Language", Arrays.asList(ODataServiceVersion.V40)), /** * The Content-Type header is used as specified in [RFC2616]. * <br/> @@ -44,76 +59,129 @@ public enum HeaderName { * <li>multipart/mixed</li> * </ul> */ - contentType("Content-Type"), + contentType("Content-Type", Arrays.asList(ODataServiceVersion.V30)), /** * This header is a custom HTTP header defined for protocol versioning purposes. This header MAY be present on any * request or response message. */ - dataServiceVersion("DataServiceVersion"), + dataServiceVersion("DataServiceVersion", Arrays.asList(ODataServiceVersion.V30)), + /** + * This header is a custom HTTP header defined for protocol versioning purposes. This header MAY be present on any + * request or response message. + */ + odataVersion("OData-Version", Arrays.asList(ODataServiceVersion.V40)), + /** + * A response to a create operation that returns 204 No Content MUST include an OData-EntityId response header. The + * value of the header is the entity-id of the entity that was acted on by the request. The syntax of the + * OData-EntityId preference is specified in [OData-ABNF]. + */ + odataEntityId("OData-EntityId", Arrays.asList(ODataServiceVersion.V40)), /** * An ETag (entity tag) is an HTTP response header returned by an HTTP/1.1 compliant web server used to determine * change in content of a resource at a given URL. The value of the header is an opaque string representing the state * of the resource at the time the response was generated. */ - etag("ETag"), + etag("ETag", Arrays.asList(ODataServiceVersion.V30, ODataServiceVersion.V40)), /** * The If-Match request-header field is used with a method to make it conditional. As specified in [RFC2616], "the * purpose of this feature is to allow efficient updates of cached information with a minimum amount of transaction * overhead. It is also used, on updating requests, to prevent inadvertent modification of the wrong version of a * resource". */ - ifMatch("If-Match"), + ifMatch("If-Match", Arrays.asList(ODataServiceVersion.V30, ODataServiceVersion.V40)), /** * The If-None-Match request header is used with a method to make it conditional. As specified in [RFC2616], "The * purpose of this feature is to allow efficient updates of cached information with a minimum amount of transaction * overhead. It is also used to prevent a method (for example, PUT) from inadvertently modifying an existing resource * when the client believes that the resource does not exist." */ - ifNoneMatch("If-None-Match"), + ifNoneMatch("If-None-Match", Arrays.asList(ODataServiceVersion.V30, ODataServiceVersion.V40)), + /** + * Clients SHOULD specify an OData-MaxVersion request header. + * <br /> + * If specified the service MUST generate a response with an OData-Version less than or equal to the specified + * OData-MaxVersion. + * <br /> + * If OData-MaxVersion is not specified, then the service SHOULD interpret the request as having an OData-MaxVersion + * equal to the maximum version supported by the service. + */ + odataMaxVersion("OData-MaxVersion", Arrays.asList(ODataServiceVersion.V40)), /** * This header is a custom HTTP request only header defined for protocol versioning purposes. This header MAY be * present on any request message from client to server. */ - maxDataServiceVersion("MaxDataServiceVersion"), + maxDataServiceVersion("MaxDataServiceVersion", Arrays.asList(ODataServiceVersion.V30)), /** * This header is a custom HTTP request only header defined for protocol versioning purposes. This header MAY be * present on any request message from client to server. */ - minDataServiceVersion("MinDataServiceVersion"), + minDataServiceVersion("MinDataServiceVersion", Arrays.asList(ODataServiceVersion.V30)), + /** + * The OData-Isolation header specifies the isolation of the current request from external changes. The only supported + * value for this header is snapshot. + * <br /> + * If the service doesn’t support OData-Isolation:snapshot and this header was specified on the request, the service + * MUST NOT process the request and MUST respond with 412 Precondition Failed. + * <br /> + * Snapshot isolation guarantees that all data returned for a request, including multiple requests within a batch or + * results retrieved across multiple pages, will be consistent as of a single point in time. Only data modifications + * made within the request (for example, by a data modification request within the same batch) are visible. The effect + * is as if the request generates a "snapshot" of the committed data as it existed at the start of the request. + * <br /> + * The OData-Isolation header may be specified on a single or batch request. If it is specified on a batch then the + * value is applied to all statements within the batch. + * <br /> + * Next links returned within a snapshot return results within the same snapshot as the initial request; the client is + * not required to repeat the header on each individual page request. + * <br /> + * The OData-Isolation header has no effect on links other than the next link. Navigation links, read links, and edit + * links return the current version of the data. + * <br /> + * A service returns 410 Gone or 404 Not Found if a consumer tries to follow a next link referring to a snapshot that + * is no longer available. + * <br /> + * The syntax of the OData-Isolation header is specified in [OData-ABNF]. + * <br /> + * A service MAY specify the support for OData-Isolation:snapshot using an annotation with term + * Capabilities.IsolationSupport, see [OData-VocCap]. + */ + odataIsolation("OData-Isolation", Arrays.asList(ODataServiceVersion.V40)), /** * A Prefer header is included in a request to state the client’s preferred, but not required, server behavior (that * is, a hint to the server). The Prefer header MAY be included on any request type (within a standalone or batch * request), and a server MAY honor the header for HTTP POST, PUT, PATCH, and MERGE requests. A Prefer header with a * value of “return-content” MUST NOT be specified on a DELETE request, a batch request as a whole, or a PUT request * to update a named stream. + * + * @see ODataPreferenceNames. */ - prefer("Prefer"), + prefer("Prefer", Arrays.asList(ODataServiceVersion.V30, ODataServiceVersion.V40)), /** * When a Prefer header value is successfully honored by the server, it MAY include a Preference-Applied response * header that states which preference values were honored by the server. */ - preferenceApplied("Preference-Applied"), + preferenceApplied("Preference-Applied", Arrays.asList(ODataServiceVersion.V30, ODataServiceVersion.V40)), /** * The DataServiceId response header is returned by the server when the response payload for an HTTP PUT, POST, PATCH, * or MERGE request is empty. The value of the header is the identifier of the entity that was acted on by the PUT, * POST, PATCH, or MERGE request. The identifier, in this case, is the same identifier that would have been returned * in the response payload (for example, as the value of the atom:id element for Atom responses) */ - dataServiceId("DataServiceId"), + dataServiceId("DataServiceId", Arrays.asList(ODataServiceVersion.V30)), /** * Location header is used to specify the URL of an entity modified through a Data Modification request, or the * request URL to check on the status of an asynchronous operation as described in * <code>202 Accepted</code>. */ - location("Location"), + location("Location", Arrays.asList(ODataServiceVersion.V30, ODataServiceVersion.V40)), /** * A service must include a * <code>Retry-After</code> header in a * <code>202 Accepted</code>. */ - retryAfter("Retry-After"), - dataServiceUrlConventions("DataServiceUrlConventions"), - slug("Slug"), + retryAfter("Retry-After", Arrays.asList(ODataServiceVersion.V30, ODataServiceVersion.V40)), + dataServiceUrlConventions("DataServiceUrlConventions", Arrays.asList(ODataServiceVersion.V30)), + slug("Slug", Arrays.asList(ODataServiceVersion.V30)), /** * This header is a custom HTTP request header. * <br/> @@ -132,12 +200,21 @@ public enum HeaderName { * <br/> * This header is only valid when on POST requests. */ - xHttpMethod("X-HTTP-METHOD"); + xHttpMethod("X-HTTP-METHOD", Arrays.asList(ODataServiceVersion.V30)); private final String headerName; - private HeaderName(final String headerName) { + private final List<ODataServiceVersion> supportedVersions; + + private HeaderName(final String headerName, final List<ODataServiceVersion> supportedVersions) { this.headerName = headerName; + this.supportedVersions = supportedVersions; + } + + final void isSupportedBy(final ODataServiceVersion serviceVersion) { + if (!supportedVersions.contains(serviceVersion)) { + throw new ODataRuntimeException("Unsupported header " + this.toString()); + } } @Override diff --git a/lib/client-api/src/main/java/org/apache/olingo/client/api/communication/header/ODataPreferences.java b/lib/client-api/src/main/java/org/apache/olingo/client/api/communication/header/ODataPreferences.java new file mode 100644 index 000000000..6afa950d2 --- /dev/null +++ b/lib/client-api/src/main/java/org/apache/olingo/client/api/communication/header/ODataPreferences.java @@ -0,0 +1,424 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ +package org.apache.olingo.client.api.communication.header; + +import java.util.Arrays; +import java.util.List; +import org.apache.olingo.commons.api.ODataRuntimeException; +import org.apache.olingo.commons.api.edm.constants.ODataServiceVersion; + +/** + * Values of the Prefer header. + */ +public class ODataPreferences { + + final ODataServiceVersion serviceVersion; + + public ODataPreferences(final ODataServiceVersion serviceVersion) { + this.serviceVersion = serviceVersion; + } + + /** + * <code>Prefer</code> header, return content. + * + * @see HeaderName#prefer + */ + public String returnContent() { + return PreferenceNames.returnContent.isSupportedBy(serviceVersion).toString(); + } + + /** + * <code>Prefer</code> header, return no content. + * + * @see HeaderName#prefer + */ + public String returnNoContent() { + return PreferenceNames.returnNoContent.isSupportedBy(serviceVersion).toString(); + } + + /** + * @see HeaderName#dataServiceUrlConventions + */ + public String keyAsSegment() { + return PreferenceNames.keyAsSegment.isSupportedBy(serviceVersion).toString(); + } + + /** + * The odata.allow-entityreferences preference indicates that the service is allowed to return entity references in + * place of entities that have previously been returned, with at least the properties requested, in the same response + * (for example, when serializing the expanded results of many-to-many relationships). The service MUST NOT return + * entity references in place of requested entities if odata.allow-entityreferences has not been specified in the + * request, unless explicitly defined by other rules in this document. The syntax of the odata.allow-entityreferences + * preference is specified in [OData-ABNF]. + * <br /> + * In the case the service applies the odata.allow-entityreferences preference it MUST include a Preference-Applied + * response header containing the odata.allow-entityreferences preference to indicate that entity references MAY be + * returned in place of entities that have previously been returned. + * <br/><br/> + * Supported by OData version 4.0 only. + * + * @see HeaderName#prefer + * @return preference. + */ + public String allowEntityReferences() { + return PreferenceNames.allowEntityReferences.isSupportedBy(serviceVersion).toString(); + } + + /** + * For scenarios in which links returned by the service are used by the client to poll for additional information, the + * client can specify the odata.callback preference to request that the service notify the client when data is + * available. + * <br /> + * The odata.callback preference can be specified: + * <ul> + * <li>when requesting asynchronous processing of a request with the respond-async preference, or</li> + * <li>on a GET request to a delta link.</li> + * </ul> + * <br /> + * The odata.callback preference MUST include the parameter url whose value is the URL of a callback endpoint to be + * invoked by the OData service when data is available. The syntax of the odata.callback preference is specified in + * [OData-ABNF]. For HTTP based callbacks, the OData service executes an HTTP GET request against the specified URL. + * <br /> + * Services that support odata.callback SHOULD support notifying the client through HTTP. Services can advertise + * callback support using the Capabilities.CallbackSupport annotation term defined in [OData-VocCap]. + * <br /> + * If the service applies the odata.callback preference it MUST include the odata.callback preference in the + * Preference-Applied response header. + * <br /> + * When the odata.callback preference is applied to asynchronous requests, the OData service invokes the callback + * endpoint once it has finished processing the request. The status monitor resource, returned in the Location header + * of the previously returned 202 Accepted response, can then be used to retrieve the results of the asynchronously + * executed request. + * <br /> + * When the odata.callback preference is specified on a GET request to a delta link and there are no changes + * available, the OData service returns a 202 Accepted response with a Location header specifying the delta link to be + * used to check for future updates. The OData service then invokes the specified callback endpoint once new changes + * become available. + * <br /> + * Combining respond-async, odata.callback and odata.track-changes preferences on a GET request to a delta-link might + * influence the response in a couple of ways. + * <ul> + * <li>If the service processes the request synchronously, and no updates are available, then the response is the same + * as if the respond-async hadn’t been specified and results in a response as described above.</li> + * <li>If the service processes the request asynchronously, then it responds with a 202 Accepted response specifying + * the URL to the status monitor resource as it would have with any other asynchronous request. Once the service has + * finished processing the asynchronous request to the delta link resource, if changes are available it invokes the + * specified callback endpoint. If no changes are available, the service SHOULD wait to notify the client until + * changes are available. Once notified, the client uses the status monitor resource from the Location header of the + * previously returned 202 Accepted response to retrieve the results. In case no updates were available after + * processing the initial request, the result will contain no updates and the client can use the delta-link contained + * in the result to retrieve the updates that have since become available.</li> + * </ul> + * <br /> + * If the consumer specifies the same URL as callback endpoint in multiple requests, the service MAY collate them into + * a single notification once additional data is available for any of the requests. However, the consumer MUST be + * prepared to deal with receiving up to as many notifications as it requested. + * <br /><br /> + * Example: using a HTTP callback endpoint to receive notification + * <br /><br /> + * Prefer: odata.callback; url="http://myserver/notfication/token/12345" + * <br/><br/> + * Supported by OData version 4.0 only. + * + * @see HeaderName#prefer + * @return preference. + */ + public String callback(final String url) { + return PreferenceNames.callback.isSupportedBy(serviceVersion).toString() + ";url=\"" + url + "\""; + } + + /** + * The odata.continue-on-error preference on a batch request is used to request that, upon encountering a request + * within the batch that returns an error, the service return the error for that request and continue processing + * additional requests within the batch. The syntax of the odata.continue-on-error preference is specified in + * [OData-ABNF]. + * <br /> + * If not specified, upon encountering an error the service MUST return the error within the batch and stop processing + * additional requests within the batch. + * <br /> + * A service MAY specify the support for the odata.continue-on-error preference using an annotation with term + * Capabilities.BatchContinueOnErrorSupported, see [OData-VocCap]. + * <br/><br/> + * Supported by OData version 4.0 only. + * + * @see HeaderName#prefer + * @return preference. + */ + public String continueOnError() { + return PreferenceNames.callback.isSupportedBy(serviceVersion).toString(); + } + + /** + * The odata.include-annotations preference in a request for data or metadata is used to specify the set of + * annotations the client requests to be included, where applicable, in the response. + * <br/> + * The value of the odata.include-annotations preference is a comma-separated list of namespaces or namespace + * qualified term names to include or exclude, with "*" representing all. The full syntax of the + * odata.include-annotations preference is defined in [OData-ABNF]. + * <br/> + * The most specific identifier always takes precedence. If the same identifier value is requested to both be excluded + * and included the behavior is undefined; the service MAY return or omit the specified vocabulary but MUST NOT raise + * an exception. + * <br/><br/> + * Example 1: a Prefer header requesting all annotations within a metadata document to be returned + * <br/><br/> + * Prefer: odata.include-annotations="*" + * <br/><br/> + * Example 2: a Prefer header requesting that no annotations are returned + * <br/><br/> + * Prefer: odata.include-annotations="-*" + * <br/><br/> + * Example 3: a Prefer header requesting that all annotations defined under the "display" namespace (recursively) be + * returned + * <br/><br/> + * Prefer: odata.include-annotations="display.*" + * <br/><br/> + * Example 4: a Prefer header requesting that the annotation with the term name subject within the display namespace + * be returned if applied + * <br/><br/> + * Prefer: odata.include-annotations="display.subject" + * <br/><br/> + * The odata.include-annotations preference is only a hint to the service. The service MAY ignore the preference and + * is free to decide whether or not to return annotations not specified in the odata.include-annotations preference. + * <br/> + * In the case that the client has specified the odata.include-annotations preference in the request, the service + * SHOULD include a Preference-Applied response header containing the odata.include-annotations preference to specify + * the annotations actually included, where applicable, in the response. This value may differ from the annotations + * requested in the Prefer header of the request. + * <br/><br/> + * Supported by OData version 4.0 only. + * + * @see HeaderName#prefer + * @return preference. + */ + public String includeAnnotations(final String value) { + return PreferenceNames.includeAnnotations.isSupportedBy(serviceVersion).toString() + "=" + value; + } + + /** + * The odata.maxpagesize preference is used to request that each collection within the response contain no more than + * the number of items specified as the positive integer value of this preference. The syntax of the odata.maxpagesize + * preference is specified in [OData-ABNF]. + * <br/><br/> + * Example: a request for customers and their orders would result in a response containing one collection with + * customer entities and for every customer a separate collection with order entities. The client could specify + * <br/> + * odata.maxpagesize=50 + * <br/>in order to request that each page of results contain a maximum of 50 customers, each with a maximum of 50 + * orders. + * <br/><br/> + * If a collection within the result contains more than the specified odata.maxpagesize, the collection SHOULD be a + * partial set of the results with a next link to the next page of results. The client MAY specify a different value + * for this preference with every request following a next link. + * <br/> + * In the example given above, the result page should include a next link for the customer collection, if there are + * more than 50 customers, and additional next links for all returned orders collections with more than 50 entities. + * <br/> + * If the client has specified the odata.maxpagesize preference in the request, and the service limits the number of + * items in collections within the response through server-driven paging, the service MAY include a Preference-Applied + * response header containing the odata.maxpagesize preference and the maximum page size applied. This value may + * differ from the value requested by the client. + * <br/><br/> + * Supported by OData version 4.0 only. + * + * @see HeaderName#prefer + * @return preference. + */ + public String maxPageSize(final int size) { + return PreferenceNames.maxPageSize.isSupportedBy(serviceVersion).toString() + "=" + size; + } + + /** + * The odata.track-changes preference is used to request that the service return a delta link that can subsequently be + * used to obtain changes (deltas) to this result. The syntax of the odata.track-changes preference is specified in + * [OData-ABNF]. + * <br /> + * For paged results, the preference MUST be specified on the initial request. Services MUST ignore the + * odata.track-changes preference if applied to the next link. + * <br /> + * The delta link MUST NOT be returned prior to the final page of results. + * <br /> + * The service includes a Preference-Applied response header in the first page of the response containing the + * odata.track-changes preference to signal that changes are being tracked. + * <br /> + * A service MAY specify the support for the odata.track-changes preference using an annotation with term + * Capabilities.ChangeTrackingSupport, see [OData-VocCap]. + * <br/><br/> + * Supported by OData version 4.0 only. + * + * @see HeaderName#prefer + * @return preference. + */ + public String trackChanges() { + return PreferenceNames.trackChanges.isSupportedBy(serviceVersion).toString(); + } + + /** + * The respond-async preference, as defined in [HTTP-Prefer], allows clients to request that the service process the + * request asynchronously. + * <br/> + * If the client has specified respond-async in the request, the service MAY process the request asynchronously and + * return a 202 Accepted response. + * <br/> + * The respond-async preference MAY be used for batch requests, but the service MUST ignore the respond-async + * preference for individual requests within a batch request. + * <br/> + * In the case that the service applies the respond-async preference it MUST include a Preference-Applied response + * header containing the respond-async preference. + * <br/> + * A service MAY specify the support for the respond-async preference using an annotation with term + * Capabilities.AsynchronousRequestsSupported, see [OData-VocCap]. + * <br/><br/> + * Example: a service receiving the following header might choose to respond + * <ul> + * <li>asynchronously if the synchronous processing of the request will take longer than 10 seconds</li> + * <li>synchronously after 5 seconds</li> + * <li>asynchronously (ignoring the wait preference)</li> + * <li>synchronously after 15 seconds (ignoring respond-async preference and the wait preference)</li> + * </ul> + * <br/> + * Prefer: respond-async, wait=10 + * <br/><br/> + * Supported by OData version 4.0 only. + * + * @see HeaderName#prefer + * @return preference. + */ + public String respondAsync() { + return PreferenceNames.respondAsync.isSupportedBy(serviceVersion).toString(); + } + + /** + * The wait preference, as defined in [HTTP-Prefer], is used to establish an upper bound on the length of time, in + * seconds, the client is prepared to wait for the service to process the request synchronously once it has been + * received. + * <br/> + * If the respond-async preference is also specified, the client requests that the service respond asynchronously + * after the specified length of time. + * <br/> + * If the respond-async preference has not been specified, the service MAY interpret the wait as a request to timeout + * after the specified period of time. + * <br/><br/> + * Supported by OData version 4.0 only. + * + * @see HeaderName#prefer + * @return preference. + */ + public String wait(final int value) { + return PreferenceNames.wait.isSupportedBy(serviceVersion).toString() + "=" + value; + } + + /** + * The return=representation and return=minimal preferences are defined in [HTTP-Prefer], + * <br/> + * In OData, return=representation or return=minimal is defined for use with a POST, PUT, or PATCH Data Modification + * Request other than to a stream property, or to an Action Request. Specifying a preference of return=representation + * or return=minimal in a GET or DELETE request, or any request to a stream property, SHOULD return a 4xx Client + * Error. + * <br/> + * A preference of return=representation or return=minimal is allowed on an individual Data Modification Request or + * Action Request within a batch, subject to the same restrictions, but SHOULD return a 4xx Client Error if specified + * on the batch request itself. + * <br/> + * A preference of return=minimal requests that the service invoke the request but does not return content in the + * response. The service MAY apply this preference by returning 204 No Content in which case it MAY include a + * Preference-Applied response header containing the return=minimal preference. + * <br/> + * A preference of return=representation requests that the service invokes the request and returns the modified + * entity. The service MAY apply this preference by returning the successfully modified resource in the body of the + * response, formatted according to the rules specified for the requested format. In this case the service MAY include + * a Preference-Applied response header containing the return=representation preference. + * <br/><br/> + * Supported by OData version 4.0 only. + * + * @see HeaderName#prefer + * @return preference. + */ + public String returnMinimal() { + return PreferenceNames.odataReturn.isSupportedBy(serviceVersion).toString() + "=minimal"; + } + + /** + * The return=representation and return=minimal preferences are defined in [HTTP-Prefer], + * <br/> + * In OData, return=representation or return=minimal is defined for use with a POST, PUT, or PATCH Data Modification + * Request other than to a stream property, or to an Action Request. Specifying a preference of return=representation + * or return=minimal in a GET or DELETE request, or any request to a stream property, SHOULD return a 4xx Client + * Error. + * <br/> + * A preference of return=representation or return=minimal is allowed on an individual Data Modification Request or + * Action Request within a batch, subject to the same restrictions, but SHOULD return a 4xx Client Error if specified + * on the batch request itself. + * <br/> + * A preference of return=minimal requests that the service invoke the request but does not return content in the + * response. The service MAY apply this preference by returning 204 No Content in which case it MAY include a + * Preference-Applied response header containing the return=minimal preference. + * <br/> + * A preference of return=representation requests that the service invokes the request and returns the modified + * entity. The service MAY apply this preference by returning the successfully modified resource in the body of the + * response, formatted according to the rules specified for the requested format. In this case the service MAY include + * a Preference-Applied response header containing the return=representation preference. + * <br/><br/> + * Supported by OData version 4.0 only. + * + * @see HeaderName#prefer + * @return preference. + */ + public String returnRepresentation() { + return PreferenceNames.odataReturn.isSupportedBy(serviceVersion).toString() + "=representation"; + } + + private static enum PreferenceNames { + + returnContent("return-content", Arrays.asList(ODataServiceVersion.V30, ODataServiceVersion.V40)), + returnNoContent("return-no-content", Arrays.asList(ODataServiceVersion.V30, ODataServiceVersion.V40)), + keyAsSegment("KeyAsSegment", Arrays.asList(ODataServiceVersion.V30, ODataServiceVersion.V40)), + allowEntityReferences("odata.allow-entityreferences", Arrays.asList(ODataServiceVersion.V40)), + callback("odata.callback", Arrays.asList(ODataServiceVersion.V40)), + continueOnError("odata.continue-on-error", Arrays.asList(ODataServiceVersion.V40)), + includeAnnotations("odata.include-annotations", Arrays.asList(ODataServiceVersion.V40)), + maxPageSize("odata.maxpagesize", Arrays.asList(ODataServiceVersion.V40)), + trackChanges("odata.track-changes", Arrays.asList(ODataServiceVersion.V40)), + respondAsync("respond-async", Arrays.asList(ODataServiceVersion.V40)), + wait("wait", Arrays.asList(ODataServiceVersion.V40)), + odataReturn("return", Arrays.asList(ODataServiceVersion.V40)); + + private final String preferenceName; + + private final List<ODataServiceVersion> supportedVersions; + + private PreferenceNames(final String preferenceName, final List<ODataServiceVersion> supportedVersions) { + this.preferenceName = preferenceName; + this.supportedVersions = supportedVersions; + } + + final PreferenceNames isSupportedBy(final ODataServiceVersion serviceVersion) { + if (!supportedVersions.contains(serviceVersion)) { + throw new ODataRuntimeException("Unsupported header " + this.toString()); + } + + return this; + } + + @Override + public String toString() { + return preferenceName; + } + } +} diff --git a/lib/client-api/src/main/java/org/apache/olingo/client/api/communication/request/ODataRequest.java b/lib/client-api/src/main/java/org/apache/olingo/client/api/communication/request/ODataRequest.java index 8ea098c14..e9a2c900d 100644 --- a/lib/client-api/src/main/java/org/apache/olingo/client/api/communication/request/ODataRequest.java +++ b/lib/client-api/src/main/java/org/apache/olingo/client/api/communication/request/ODataRequest.java @@ -21,16 +21,20 @@ package org.apache.olingo.client.api.communication.request; import java.io.InputStream; import java.net.URI; import java.util.Collection; +import org.apache.olingo.client.api.communication.header.HeaderName; import org.apache.olingo.client.api.http.HttpMethod; /** * Abstract representation of an OData request. Get instance by using factories. * - * @see CUDRequestFactory - * @see RetrieveRequestFactory - * @see BatchRequestFactory - * @see InvokeRequestFactory - * @see StreamedRequestFactory + * @see org.apache.olingo.client.api.communication.request.cud.v3.CUDRequestFactory + * @see org.apache.olingo.client.api.communication.request.cud.v4.CUDRequestFactory + * @see org.apache.olingo.client.api.communication.request.batch.v3.BatchRequestFactory + * @see org.apache.olingo.client.api.communication.request.batch.v4.BatchRequestFactory + * @see org.apache.olingo.client.api.communication.request.invoke.v3.InvokeRequestFactory + * @see org.apache.olingo.client.api.communication.request.invoke.v4.InvokeRequestFactory + * @see org.apache.olingo.client.api.communication.request.streamed.v3.StreamedRequestFactory + * @see org.apache.olingo.client.api.communication.request.streamed.v4.StreamedRequestFactory */ public interface ODataRequest { @@ -175,6 +179,16 @@ public interface ODataRequest { */ ODataRequest addCustomHeader(final String name, final String value); + /** + * Adds a custom OData request header. The method fails in case of the header name is not supported by the current + * working version. + * + * @param name header name. + * @param value header value. + * @return current object + */ + ODataRequest addCustomHeader(final HeaderName name, final String value); + /** * Gets byte array representation of the full request header. * diff --git a/lib/client-api/src/main/java/org/apache/olingo/client/api/uri/SegmentType.java b/lib/client-api/src/main/java/org/apache/olingo/client/api/uri/SegmentType.java index a3e5a1577..5da7f09d2 100644 --- a/lib/client-api/src/main/java/org/apache/olingo/client/api/uri/SegmentType.java +++ b/lib/client-api/src/main/java/org/apache/olingo/client/api/uri/SegmentType.java @@ -34,6 +34,7 @@ public enum SegmentType { NAVIGATION, DERIVED_ENTITY_TYPE, VALUE("$value"), + COUNT("$count"), BOUND_OPERATION, UNBOUND_OPERATION, METADATA("$metadata"), diff --git a/lib/client-api/src/main/java/org/apache/olingo/client/api/uri/v3/URIBuilder.java b/lib/client-api/src/main/java/org/apache/olingo/client/api/uri/v3/URIBuilder.java index 72a0c03dd..d81edbf30 100644 --- a/lib/client-api/src/main/java/org/apache/olingo/client/api/uri/v3/URIBuilder.java +++ b/lib/client-api/src/main/java/org/apache/olingo/client/api/uri/v3/URIBuilder.java @@ -42,8 +42,7 @@ public interface URIBuilder extends CommonURIBuilder<URIBuilder> { * * @param inlineCount value * @return current URIBuilder instance - * @see QueryOption#INLINECOUNT + * @see org.apache.olingo.client.api.uri.QueryOption#INLINECOUNT */ URIBuilder inlineCount(InlineCount inlineCount); - } diff --git a/lib/client-api/src/main/java/org/apache/olingo/client/api/uri/v4/URIBuilder.java b/lib/client-api/src/main/java/org/apache/olingo/client/api/uri/v4/URIBuilder.java index 63d3a01bf..5f06a3241 100644 --- a/lib/client-api/src/main/java/org/apache/olingo/client/api/uri/v4/URIBuilder.java +++ b/lib/client-api/src/main/java/org/apache/olingo/client/api/uri/v4/URIBuilder.java @@ -20,9 +20,9 @@ package org.apache.olingo.client.api.uri.v4; import java.util.Map; import org.apache.commons.lang3.tuple.Pair; -import org.apache.olingo.client.api.uri.CommonURIBuilder; import org.apache.olingo.client.api.uri.QueryOption; import org.apache.olingo.commons.api.edm.EdmEnumType; +import org.apache.olingo.client.api.uri.CommonURIBuilder; public interface URIBuilder extends CommonURIBuilder<URIBuilder> { @@ -87,7 +87,7 @@ public interface URIBuilder extends CommonURIBuilder<URIBuilder> { * * @param idValue opaque token. * @return current URIBuilder instance - * @see QueryOption#ID + * @see org.apache.olingo.client.api.uri.QueryOption#ID */ URIBuilder id(String idValue); @@ -96,7 +96,7 @@ public interface URIBuilder extends CommonURIBuilder<URIBuilder> { * * @param value true or false * @return current URIBuilder instance - * @see QueryOption#COUNT + * @see org.apache.olingo.client.api.uri.QueryOption#COUNT */ URIBuilder count(boolean value); @@ -105,7 +105,19 @@ public interface URIBuilder extends CommonURIBuilder<URIBuilder> { * * @param expression search expression * @return current URIBuilder instance - * @see QueryOption#SEARCH + * @see org.apache.olingo.client.api.uri.QueryOption#SEARCH */ URIBuilder search(String expression); + + /** + * The set of expanded entities can be refined through the application of expand options, expressed as a + * semicolon-separated list of system query options, enclosed in parentheses, see [OData-URL]. + * + * @param expandItem item to be expanded. + * @param options System query options. Allowed query options are: $filter, $select, $orderby, $skip, $top, $count, + * $search, $expand, and $levels. + * @return current URIBuilder instance. + * @see org.apache.olingo.client.api.uri.QueryOption#EXPAND + */ + URIBuilder expandWithOptions(String expandItem, Map<String, Object> options); } diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/ODataRequestImpl.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/ODataRequestImpl.java index 3e6c1bbd3..0a8c495f8 100644 --- a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/ODataRequestImpl.java +++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/ODataRequestImpl.java @@ -38,14 +38,10 @@ import org.apache.olingo.client.api.v3.Configuration; import org.apache.olingo.client.api.communication.ODataClientErrorException; import org.apache.olingo.client.api.communication.ODataServerErrorException; import org.apache.olingo.client.api.communication.header.HeaderName; -import org.apache.olingo.client.api.communication.header.ODataHeaderValues; import org.apache.olingo.client.api.communication.header.ODataHeaders; +import org.apache.olingo.client.api.communication.header.ODataPreferences; import org.apache.olingo.client.api.communication.request.ODataRequest; import org.apache.olingo.client.api.communication.request.ODataStreamer; -import org.apache.olingo.client.api.communication.request.batch.v3.BatchRequestFactory; -import org.apache.olingo.client.api.communication.request.cud.v3.CUDRequestFactory; -import org.apache.olingo.client.api.communication.request.invoke.v3.InvokeRequestFactory; -import org.apache.olingo.client.api.communication.request.streamed.v3.StreamedRequestFactory; import org.apache.olingo.client.api.communication.response.ODataResponse; import org.apache.olingo.commons.api.format.Format; import org.apache.olingo.client.api.http.HttpClientException; @@ -66,10 +62,14 @@ import org.slf4j.LoggerFactory; * * @param <T> Accepted content-type formats by the request in object. * - * @see CUDRequestFactory - * @see BatchRequestFactory - * @see InvokeRequestFactory - * @see StreamedRequestFactory + * @see org.apache.olingo.client.api.communication.request.cud.v3.CUDRequestFactory + * @see org.apache.olingo.client.api.communication.request.cud.v4.CUDRequestFactory + * @see org.apache.olingo.client.api.communication.request.batch.v3.BatchRequestFactory + * @see org.apache.olingo.client.api.communication.request.batch.v4.BatchRequestFactory + * @see org.apache.olingo.client.api.communication.request.invoke.v3.InvokeRequestFactory + * @see org.apache.olingo.client.api.communication.request.invoke.v4.InvokeRequestFactory + * @see org.apache.olingo.client.api.communication.request.streamed.v3.StreamedRequestFactory + * @see org.apache.olingo.client.api.communication.request.streamed.v4.StreamedRequestFactory */ public class ODataRequestImpl<T extends Format> implements ODataRequest { @@ -250,6 +250,15 @@ public class ODataRequestImpl<T extends Format> implements ODataRequest { return this; } + /** + * {@inheritDoc} + */ + @Override + public ODataRequest addCustomHeader(final HeaderName name, final String value) { + odataHeaders.setHeader(name, value); + return this; + } + /** * {@inheritDoc} */ @@ -384,7 +393,8 @@ public class ODataRequestImpl<T extends Format> implements ODataRequest { if (odataClient.getServiceVersion() == ODataServiceVersion.V30 && ((Configuration) odataClient.getConfiguration()).isKeyAsSegment()) { addCustomHeader( - HeaderName.dataServiceUrlConventions.toString(), ODataHeaderValues.keyAsSegment); + HeaderName.dataServiceUrlConventions.toString(), + new ODataPreferences(odataClient.getServiceVersion()).keyAsSegment()); } // Add all available headers diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/data/JSONServiceDocumentDeserializer.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/data/JSONServiceDocumentDeserializer.java index 143aed88d..8594f909d 100644 --- a/lib/client-core/src/main/java/org/apache/olingo/client/core/data/JSONServiceDocumentDeserializer.java +++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/data/JSONServiceDocumentDeserializer.java @@ -57,7 +57,7 @@ public class JSONServiceDocumentDeserializer extends ODataJacksonDeserializer<Ab setMetadataContext(tree.get(Constants.JSON_CONTEXT).textValue()); } - for (final Iterator<JsonNode> itor = tree.get(Constants.JSON_VALUE).elements(); itor.hasNext();) { + for (final Iterator<JsonNode> itor = tree.get(Constants.VALUE).elements(); itor.hasNext();) { final JsonNode node = itor.next(); final ServiceDocumentItemImpl item = new ServiceDocumentItemImpl(); diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/op/AbstractODataBinder.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/op/AbstractODataBinder.java index 746b016d3..a678834e6 100644 --- a/lib/client-core/src/main/java/org/apache/olingo/client/core/op/AbstractODataBinder.java +++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/op/AbstractODataBinder.java @@ -88,21 +88,22 @@ public abstract class AbstractODataBinder implements CommonODataBinder { } @Override - public Feed getFeed(final ODataEntitySet feed, final Class<? extends Feed> reference) { - final Feed feedResource = ResourceFactory.newFeed(reference); + public Feed getFeed(final ODataEntitySet entitySet, final Class<? extends Feed> reference) { + final Feed feed = ResourceFactory.newFeed(reference); - feedResource.setCount(feed.getCount()); + feed.setContextURL(entitySet.getContextURL()); + feed.setCount(entitySet.getCount()); - final URI next = feed.getNext(); + final URI next = entitySet.getNext(); if (next != null) { - feedResource.setNext(next); + feed.setNext(next); } - for (ODataEntity entity : feed.getEntities()) { - feedResource.getEntries().add(getEntry(entity, ResourceFactory.entryClassForFeed(reference))); + for (ODataEntity entity : entitySet.getEntities()) { + feed.getEntries().add(getEntry(entity, ResourceFactory.entryClassForFeed(reference))); } - return feedResource; + return feed; } @Override @@ -113,6 +114,9 @@ public abstract class AbstractODataBinder implements CommonODataBinder { @Override public Entry getEntry(final ODataEntity entity, final Class<? extends Entry> reference, final boolean setType) { final Entry entry = ResourceFactory.newEntry(reference); + + entry.setContextURL(entity.getContextURL()); + entry.setId(entity.getReference()); entry.setType(entity.getName()); // ------------------------------------------------------------- @@ -277,6 +281,8 @@ public abstract class AbstractODataBinder implements CommonODataBinder { ? client.getObjectFactory().newEntitySet() : client.getObjectFactory().newEntitySet(URIUtils.getURI(base, next.toASCIIString())); + entitySet.setContextURL(resource.getContextURL()); + if (resource.getCount() != null) { entitySet.setCount(resource.getCount()); } @@ -309,6 +315,9 @@ public abstract class AbstractODataBinder implements CommonODataBinder { : client.getObjectFactory().newEntity(resource.getType(), URIUtils.getURI(base, resource.getSelfLink().getHref())); + entity.setContextURL(resource.getContextURL()); + entity.setReference(resource.getId()); + if (StringUtils.isNotBlank(resource.getETag())) { entity.setETag(resource.getETag()); } @@ -328,7 +337,7 @@ public abstract class AbstractODataBinder implements CommonODataBinder { if (inlineEntry == null && inlineFeed == null) { entity.addLink( client.getObjectFactory().newEntityNavigationLink(link.getTitle(), base, link.getHref())); - } else if (inlineFeed == null) { + } else if (inlineEntry != null) { entity.addLink(client.getObjectFactory().newInlineEntity( link.getTitle(), base, link.getHref(), getODataEntity(inlineEntry, diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/uri/AbstractURIBuilder.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/uri/AbstractURIBuilder.java index 6cc27e3d5..906b62ca3 100644 --- a/lib/client-core/src/main/java/org/apache/olingo/client/core/uri/AbstractURIBuilder.java +++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/uri/AbstractURIBuilder.java @@ -21,6 +21,7 @@ package org.apache.olingo.client.core.uri; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; +import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -61,9 +62,7 @@ public abstract class AbstractURIBuilder<UB extends CommonURIBuilder<?>> impleme public String getValue() { return value; } - } - private final ODataServiceVersion version; protected final List<Segment> segments = new ArrayList<Segment>(); @@ -115,17 +114,11 @@ public abstract class AbstractURIBuilder<UB extends CommonURIBuilder<?>> impleme @Override public UB appendKeySegment(final Map<String, Object> segmentValues) { - if (segmentValues == null || segmentValues.isEmpty()) { + final String key = buildMultiKeySegment(segmentValues, true); + if (StringUtils.isEmpty(key)) { segments.add(new Segment(SegmentType.KEY, noKeysWrapper())); } else { - final StringBuilder keyBuilder = new StringBuilder().append('('); - for (Map.Entry<String, Object> entry : segmentValues.entrySet()) { - keyBuilder.append(entry.getKey()).append('=').append(URIUtils.escape(version, entry.getValue())); - keyBuilder.append(','); - } - keyBuilder.deleteCharAt(keyBuilder.length() - 1).append(')'); - - segments.add(new Segment(SegmentType.KEY, keyBuilder.toString())); + segments.add(new Segment(SegmentType.KEY, key)); } return getThis(); @@ -183,7 +176,14 @@ public abstract class AbstractURIBuilder<UB extends CommonURIBuilder<?>> impleme @Override public UB expand(final String... expandItems) { - return addQueryOption(QueryOption.EXPAND, StringUtils.join(expandItems, ",")); + final List<String> values = new ArrayList<String>(); + if (queryOptions.containsKey(QueryOption.EXPAND.toString())) { + values.add(queryOptions.get(QueryOption.EXPAND.toString())); + } + + values.addAll(Arrays.asList(expandItems)); + + return addQueryOption(QueryOption.EXPAND, StringUtils.join(values, ",")); } @Override @@ -203,7 +203,14 @@ public abstract class AbstractURIBuilder<UB extends CommonURIBuilder<?>> impleme @Override public UB select(final String... selectItems) { - return addQueryOption(QueryOption.SELECT, StringUtils.join(selectItems, ",")); + final List<String> values = new ArrayList<String>(); + if (queryOptions.containsKey(QueryOption.SELECT.toString())) { + values.add(queryOptions.get(QueryOption.SELECT.toString())); + } + + values.addAll(Arrays.asList(selectItems)); + + return addQueryOption(QueryOption.SELECT, StringUtils.join(values, ",")); } @Override @@ -275,4 +282,19 @@ public abstract class AbstractURIBuilder<UB extends CommonURIBuilder<?>> impleme return build().toASCIIString(); } + protected String buildMultiKeySegment(final Map<String, Object> segmentValues, final boolean escape) { + if (segmentValues == null || segmentValues.isEmpty()) { + return StringUtils.EMPTY; + } else { + final StringBuilder keyBuilder = new StringBuilder().append('('); + for (Map.Entry<String, Object> entry : segmentValues.entrySet()) { + keyBuilder.append(entry.getKey()).append('=').append( + escape ? URIUtils.escape(version, entry.getValue()) : entry.getValue()); + keyBuilder.append(','); + } + keyBuilder.deleteCharAt(keyBuilder.length() - 1).append(')'); + + return keyBuilder.toString(); + } + } } diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/uri/v4/URIBuilderImpl.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/uri/v4/URIBuilderImpl.java index e996322b2..332f8c2aa 100644 --- a/lib/client-core/src/main/java/org/apache/olingo/client/core/uri/v4/URIBuilderImpl.java +++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/uri/v4/URIBuilderImpl.java @@ -101,11 +101,6 @@ public class URIBuilderImpl extends AbstractURIBuilder<URIBuilder> implements UR return getThis(); } - @Override - public URIBuilder count(final boolean value) { - return addQueryOption(QueryOption.COUNT, Boolean.toString(value)); - } - @Override public URIBuilder appendAllSegment() { segments.add(new Segment(SegmentType.ALL, SegmentType.ALL.getValue())); @@ -122,4 +117,13 @@ public class URIBuilderImpl extends AbstractURIBuilder<URIBuilder> implements UR return addQueryOption(QueryOption.SEARCH, expression); } + @Override + public URIBuilder count(final boolean value) { + return addQueryOption(QueryOption.COUNT, Boolean.toString(value)); + } + + @Override + public URIBuilder expandWithOptions(final String expandItem, final Map<String, Object> options) { + return expand(expandItem + buildMultiKeySegment(options, false)); + } } diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/v4/ODataClientImpl.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/v4/ODataClientImpl.java index 834f6f0d7..a5aafabac 100644 --- a/lib/client-core/src/main/java/org/apache/olingo/client/core/v4/ODataClientImpl.java +++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/v4/ODataClientImpl.java @@ -82,8 +82,8 @@ public class ODataClientImpl extends AbstractODataClient implements ODataClient @Override public ODataHeaders getVersionHeaders() { final ODataHeadersImpl odataHeaders = new ODataHeadersImpl(); - odataHeaders.setHeader(HeaderName.maxDataServiceVersion, ODataServiceVersion.V40.toString()); - odataHeaders.setHeader(HeaderName.dataServiceVersion, ODataServiceVersion.V40.toString()); + odataHeaders.setHeader(HeaderName.odataMaxVersion, ODataServiceVersion.V40.toString()); + odataHeaders.setHeader(HeaderName.odataVersion, ODataServiceVersion.V40.toString()); return odataHeaders; } diff --git a/lib/client-core/src/test/java/org/apache/olingo/client/core/it/v3/EntityCreateTestITCase.java b/lib/client-core/src/test/java/org/apache/olingo/client/core/it/v3/EntityCreateTestITCase.java index 8146830b5..1c331e52b 100644 --- a/lib/client-core/src/test/java/org/apache/olingo/client/core/it/v3/EntityCreateTestITCase.java +++ b/lib/client-core/src/test/java/org/apache/olingo/client/core/it/v3/EntityCreateTestITCase.java @@ -30,7 +30,7 @@ import java.util.LinkedHashMap; import java.util.Set; import org.apache.http.entity.ContentType; import org.apache.olingo.client.api.communication.header.HeaderName; -import org.apache.olingo.client.api.communication.header.ODataHeaderValues; +import org.apache.olingo.client.api.communication.header.ODataPreferences; import org.apache.olingo.client.api.communication.request.UpdateType; import org.apache.olingo.client.api.communication.request.cud.ODataDeleteRequest; import org.apache.olingo.client.api.communication.request.cud.ODataEntityCreateRequest; @@ -229,11 +229,11 @@ public class EntityCreateTestITCase extends AbstractTestITCase { final ODataEntityCreateRequest createReq = client.getCUDRequestFactory().getEntityCreateRequest( client.getURIBuilder(getServiceRoot()).appendEntitySetSegment("Customer").build(), original); - createReq.setPrefer(ODataHeaderValues.preferReturnNoContent); + createReq.setPrefer(new ODataPreferences(client.getServiceVersion()).returnNoContent()); final ODataEntityCreateResponse createRes = createReq.execute(); assertEquals(204, createRes.getStatusCode()); - assertEquals(ODataHeaderValues.preferReturnNoContent, + assertEquals(new ODataPreferences(client.getServiceVersion()).returnNoContent(), createRes.getHeader(HeaderName.preferenceApplied).iterator().next()); try { @@ -260,7 +260,7 @@ public class EntityCreateTestITCase extends AbstractTestITCase { client.getCUDRequestFactory().getEntityCreateRequest(uriBuilder.build(), original); createReq.setFormat(ODataPubFormat.JSON_FULL_METADATA); createReq.setContentType(ContentType.APPLICATION_ATOM_XML.getMimeType()); - createReq.setPrefer(ODataHeaderValues.preferReturnContent); + createReq.setPrefer(new ODataPreferences(client.getServiceVersion()).returnContent()); try { final ODataEntityCreateResponse createRes = createReq.execute(); diff --git a/lib/client-core/src/test/java/org/apache/olingo/client/core/it/v3/EntityUpdateTestITCase.java b/lib/client-core/src/test/java/org/apache/olingo/client/core/it/v3/EntityUpdateTestITCase.java index 129f086a8..8a9f93f91 100644 --- a/lib/client-core/src/test/java/org/apache/olingo/client/core/it/v3/EntityUpdateTestITCase.java +++ b/lib/client-core/src/test/java/org/apache/olingo/client/core/it/v3/EntityUpdateTestITCase.java @@ -26,7 +26,7 @@ import java.net.URI; import java.util.LinkedHashMap; import org.apache.olingo.client.api.communication.ODataClientErrorException; import org.apache.olingo.client.api.communication.header.HeaderName; -import org.apache.olingo.client.api.communication.header.ODataHeaderValues; +import org.apache.olingo.client.api.communication.header.ODataPreferences; import org.apache.olingo.client.api.communication.request.UpdateType; import org.apache.olingo.client.api.communication.request.cud.ODataEntityUpdateRequest; import org.apache.olingo.client.api.communication.request.retrieve.ODataEntityRequest; @@ -210,11 +210,11 @@ public class EntityUpdateTestITCase extends AbstractTestITCase { @Test public void updateReturnContent() throws EdmPrimitiveTypeException { final ODataEntityUpdateRequest req = buildMultiKeyUpdateReq(client.getConfiguration().getDefaultPubFormat()); - req.setPrefer(ODataHeaderValues.preferReturnContent); + req.setPrefer(new ODataPreferences(client.getServiceVersion()).returnContent()); final ODataEntityUpdateResponse res = req.execute(); assertEquals(200, res.getStatusCode()); - assertEquals(ODataHeaderValues.preferReturnContent, + assertEquals(new ODataPreferences(client.getServiceVersion()).returnContent(), res.getHeader(HeaderName.preferenceApplied).iterator().next()); assertNotNull(res.getBody()); } diff --git a/lib/client-core/src/test/java/org/apache/olingo/client/core/it/v3/PropertyValueTestITCase.java b/lib/client-core/src/test/java/org/apache/olingo/client/core/it/v3/PropertyValueTestITCase.java index b8dd0a831..3660cad7c 100644 --- a/lib/client-core/src/test/java/org/apache/olingo/client/core/it/v3/PropertyValueTestITCase.java +++ b/lib/client-core/src/test/java/org/apache/olingo/client/core/it/v3/PropertyValueTestITCase.java @@ -100,11 +100,11 @@ public class PropertyValueTestITCase extends AbstractTestITCase { req.setAccept("application/json"); ODataRetrieveResponse<ODataEntity> res = req.execute(); assertEquals(200, res.getStatusCode()); - ODataEntity entitySet = res.getBody(); - assertNotNull(entitySet); + ODataEntity entity = res.getBody(); + assertNotNull(entity); assertEquals("fi653p3+MklA/LdoBlhWgnMTUUEo8tEgtbMXnF0a3CUNL9BZxXpSRiD9ebTnmNR0zWPjJ" + "VIDx4tdmCnq55XrJh+RW9aI/b34wAogK3kcORw=", - entitySet.getProperties().get(0).getValue().toString()); + entity.getProperties().get(0).getValue().toString()); } @Test(expected = ODataClientErrorException.class) diff --git a/lib/client-core/src/test/java/org/apache/olingo/client/core/it/v4/EntityRetrieveTestITCase.java b/lib/client-core/src/test/java/org/apache/olingo/client/core/it/v4/EntityRetrieveTestITCase.java new file mode 100644 index 000000000..a96384fb9 --- /dev/null +++ b/lib/client-core/src/test/java/org/apache/olingo/client/core/it/v4/EntityRetrieveTestITCase.java @@ -0,0 +1,280 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ +package org.apache.olingo.client.core.it.v4; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.LinkedHashMap; +import java.util.List; +import org.apache.commons.lang3.StringUtils; +import org.apache.olingo.client.api.communication.request.retrieve.ODataEntityRequest; +import org.apache.olingo.client.api.communication.request.retrieve.ODataRawRequest; +import org.apache.olingo.client.api.communication.response.ODataRawResponse; +import org.apache.olingo.client.api.communication.response.ODataRetrieveResponse; +import org.apache.olingo.commons.api.domain.ODataEntity; +import org.apache.olingo.commons.api.domain.ODataEntitySet; +import org.apache.olingo.commons.api.domain.ODataInlineEntity; +import org.apache.olingo.commons.api.domain.ODataInlineEntitySet; +import org.apache.olingo.commons.api.domain.ODataLink; +import org.apache.olingo.commons.api.domain.ODataProperty; +import org.apache.olingo.commons.api.format.ODataPubFormat; +import org.apache.olingo.client.api.uri.CommonURIBuilder; +import org.apache.olingo.commons.core.op.ResourceFactory; +import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeException; +import org.junit.Ignore; +import org.junit.Test; + +/** + * This is the unit test class to check entity retrieve operations. + */ +public class EntityRetrieveTestITCase extends AbstractTestITCase { + + protected String getServiceRoot() { + return testStaticServiceRootURL; + } + + private void withInlineEntry(final ODataPubFormat format) { + final CommonURIBuilder<?> uriBuilder = client.getURIBuilder(getServiceRoot()). + appendEntitySetSegment("Customers").appendKeySegment(1).expand("Company"); + + final ODataEntityRequest req = client.getRetrieveRequestFactory().getEntityRequest(uriBuilder.build()); + req.setFormat(format); + + final ODataRetrieveResponse<ODataEntity> res = req.execute(); + final ODataEntity entity = res.getBody(); + + assertNotNull(entity); + assertEquals("#Microsoft.Test.OData.Services.ODataWCFService.Customer", entity.getName()); + assertEquals(getServiceRoot() + "/Customers(PersonID=1)", entity.getEditLink().toASCIIString()); + + assertEquals(3, entity.getNavigationLinks().size()); + assertTrue(entity.getAssociationLinks().isEmpty()); + + boolean found = false; + + for (ODataLink link : entity.getNavigationLinks()) { + if (link instanceof ODataInlineEntity) { + final ODataEntity inline = ((ODataInlineEntity) link).getEntity(); + assertNotNull(inline); + + debugEntry(client.getBinder().getEntry( + inline, ResourceFactory.entryClassForFormat(format == ODataPubFormat.ATOM)), "Just read"); + + final List<ODataProperty> properties = inline.getProperties(); + assertEquals(5, properties.size()); + + assertTrue(properties.get(0).getName().equals("CompanyID") + || properties.get(1).getName().equals("CompanyID") + || properties.get(2).getName().equals("CompanyID") + || properties.get(3).getName().equals("CompanyID") + || properties.get(4).getName().equals("CompanyID")); + assertTrue(properties.get(0).getValue().toString().equals("0") + || properties.get(1).getValue().toString().equals("0") + || properties.get(2).getValue().toString().equals("0") + || properties.get(3).getValue().toString().equals("0") + || properties.get(4).getValue().toString().equals("0")); + + found = true; + } + } + + assertTrue(found); + } + + @Test + public void withInlineEntryFromAtom() { + withInlineEntry(ODataPubFormat.ATOM); + } + + @Test + @Ignore + public void withInlineEntryFromJSON() { + // this needs to be full, otherwise there is no mean to recognize links + withInlineEntry(ODataPubFormat.JSON_FULL_METADATA); + } + + private void withInlineFeed(final ODataPubFormat format) { + final CommonURIBuilder<?> uriBuilder = client.getURIBuilder(getServiceRoot()). + appendEntitySetSegment("Customers").appendKeySegment(1).expand("Orders"); + + final ODataEntityRequest req = client.getRetrieveRequestFactory().getEntityRequest(uriBuilder.build()); + req.setFormat(format); + + final ODataRetrieveResponse<ODataEntity> res = req.execute(); + final ODataEntity entity = res.getBody(); + assertNotNull(entity); + + boolean found = false; + + for (ODataLink link : entity.getNavigationLinks()) { + if (link instanceof ODataInlineEntitySet) { + final ODataEntitySet inline = ((ODataInlineEntitySet) link).getEntitySet(); + assertNotNull(inline); + + debugFeed(client.getBinder().getFeed(inline, ResourceFactory.feedClassForFormat( + format == ODataPubFormat.ATOM)), "Just read"); + + found = true; + } + } + + assertTrue(found); + } + + @Test + public void withInlineFeedFromAtom() { + withInlineFeed(ODataPubFormat.ATOM); + } + + @Test + @Ignore + public void withInlineFeedFromJSON() { + // this needs to be full, otherwise there is no mean to recognize links + withInlineFeed(ODataPubFormat.JSON_FULL_METADATA); + } + + private void rawRequest(final ODataPubFormat format) { + final CommonURIBuilder<?> uriBuilder = client.getURIBuilder(getServiceRoot()). + appendEntitySetSegment("People").appendKeySegment(5); + + final ODataRawRequest req = client.getRetrieveRequestFactory().getRawRequest(uriBuilder.build()); + req.setFormat(format.toString(client.getServiceVersion())); + + final ODataRawResponse res = req.execute(); + assertNotNull(res); + + final ODataEntitySet entitySet = res.getBodyAs(ODataEntitySet.class); + assertNull(entitySet); + + final ODataEntity entity = res.getBodyAs(ODataEntity.class); + assertTrue(entity.getReference().endsWith("/StaticService/V40/Static.svc/People(5)")); + } + + @Test + public void rawRequestAsAtom() { + rawRequest(ODataPubFormat.ATOM); + } + + @Test + @Ignore + public void rawRequestAsJSON() { + // this needs to be full, otherwise actions will not be provided + rawRequest(ODataPubFormat.JSON_FULL_METADATA); + } + + private void multiKey(final ODataPubFormat format) throws EdmPrimitiveTypeException { + final LinkedHashMap<String, Object> multiKey = new LinkedHashMap<String, Object>(); + multiKey.put("ProductID", "6"); + multiKey.put("ProductDetailID", 1); + + final CommonURIBuilder<?> uriBuilder = client.getURIBuilder(getServiceRoot()). + appendEntitySetSegment("ProductDetails").appendKeySegment(multiKey); + + final ODataEntityRequest req = client.getRetrieveRequestFactory().getEntityRequest(uriBuilder.build()); + req.setFormat(format); + + final ODataRetrieveResponse<ODataEntity> res = req.execute(); + final ODataEntity entity = res.getBody(); + assertNotNull(entity); + assertEquals(Integer.valueOf(1), + entity.getProperty("ProductDetailID").getPrimitiveValue().toCastValue(Integer.class)); + } + + @Test + public void multiKeyAsAtom() throws EdmPrimitiveTypeException { + multiKey(ODataPubFormat.ATOM); + } + + @Test + @Ignore + public void multiKeyAsJSON() throws EdmPrimitiveTypeException { + multiKey(ODataPubFormat.JSON_FULL_METADATA); + } + + @Test + public void checkForETagAsATOM() { + checkForETag(ODataPubFormat.ATOM); + } + + @Test + @Ignore + public void checkForETagAsJSON() { + checkForETag(ODataPubFormat.JSON_FULL_METADATA); + } + + private void checkForETag(final ODataPubFormat format) { + final CommonURIBuilder<?> uriBuilder = + client.getURIBuilder(getServiceRoot()).appendEntitySetSegment("Orders").appendKeySegment(8); + + final ODataEntityRequest req = client.getRetrieveRequestFactory().getEntityRequest(uriBuilder.build()); + req.setFormat(format); + + final ODataRetrieveResponse<ODataEntity> res = req.execute(); + assertEquals(200, res.getStatusCode()); + + final String etag = res.getEtag(); + assertTrue(StringUtils.isNotBlank(etag)); + + final ODataEntity order = res.getBody(); + assertEquals(etag, order.getETag()); + } + + @Test(expected = IllegalArgumentException.class) + @Ignore + public void issue99() { + final CommonURIBuilder<?> uriBuilder = client.getURIBuilder(getServiceRoot()).appendEntitySetSegment("Car"); + + final ODataEntityRequest req = client.getRetrieveRequestFactory().getEntityRequest(uriBuilder.build()); + req.setFormat(ODataPubFormat.JSON); + + // this statement should cause an IllegalArgumentException bearing JsonParseException + // since we are attempting to parse an EntitySet as if it was an Entity + req.execute().getBody(); + } + + @Test + public void retrieveEntityReferenceAsAtom() { + retrieveEntityReference(ODataPubFormat.ATOM); + } + + @Test + @Ignore + public void retrieveEntityReferenceAsJSON() { + retrieveEntityReference(ODataPubFormat.JSON_FULL_METADATA); + } + + private void retrieveEntityReference(final ODataPubFormat format) { + final CommonURIBuilder<?> uriBuilder = client.getURIBuilder(getServiceRoot()). + appendEntitySetSegment("Orders").appendKeySegment(8).appendNavigationSegment("CustomerForOrder"). + appendRefSegment(); + + final ODataEntityRequest req = client.getRetrieveRequestFactory().getEntityRequest(uriBuilder.build()); + req.setFormat(format); + + final ODataRetrieveResponse<ODataEntity> res = req.execute(); + assertNotNull(res); + + final ODataEntity entity = res.getBody(); + assertNotNull(entity); + assertTrue(entity.getReference().endsWith("/StaticService/V40/Static.svc/Customers(PersonID=1)")); + } +} diff --git a/lib/client-core/src/test/java/org/apache/olingo/client/core/it/v4/EntitySetTestITCase.java b/lib/client-core/src/test/java/org/apache/olingo/client/core/it/v4/EntitySetTestITCase.java index 8455ecdcc..473e7211a 100644 --- a/lib/client-core/src/test/java/org/apache/olingo/client/core/it/v4/EntitySetTestITCase.java +++ b/lib/client-core/src/test/java/org/apache/olingo/client/core/it/v4/EntitySetTestITCase.java @@ -104,6 +104,8 @@ public class EntitySetTestITCase extends AbstractTestITCase { assertNotNull(feed); + assertTrue(feed.getContextURL().toASCIIString().endsWith("$metadata#People")); + debugFeed(client.getBinder().getFeed(feed, ResourceFactory.feedClassForFormat( ODataPubFormat.ATOM == format)), "Just retrieved feed"); @@ -151,5 +153,6 @@ public class EntitySetTestITCase extends AbstractTestITCase { final ODataEntitySet entitySet = res.getBodyAs(ODataEntitySet.class); assertNotNull(entitySet); + assertTrue(entitySet.getContextURL().toASCIIString().endsWith("$metadata#People")); } } diff --git a/lib/client-core/src/test/java/org/apache/olingo/client/core/it/v4/PropertyValueTestITCase.java b/lib/client-core/src/test/java/org/apache/olingo/client/core/it/v4/PropertyValueTestITCase.java new file mode 100644 index 000000000..3a8b9e979 --- /dev/null +++ b/lib/client-core/src/test/java/org/apache/olingo/client/core/it/v4/PropertyValueTestITCase.java @@ -0,0 +1,144 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ +package org.apache.olingo.client.core.it.v4; + +import static org.junit.Assert.*; +import java.io.IOException; +import org.apache.commons.lang3.StringUtils; +import org.apache.olingo.client.api.communication.ODataClientErrorException; +import org.apache.olingo.client.api.communication.request.retrieve.ODataPropertyRequest; +import org.apache.olingo.client.api.communication.request.retrieve.ODataValueRequest; +import org.apache.olingo.commons.api.format.ODataValueFormat; +import org.apache.olingo.client.api.uri.CommonURIBuilder; +import org.apache.olingo.commons.api.domain.ODataPrimitiveValue; +import org.apache.olingo.commons.api.domain.ODataProperty; +import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeException; +import org.apache.olingo.commons.api.edm.constants.ODataServiceVersion; +import org.apache.olingo.commons.api.format.ODataFormat; +import org.apache.olingo.commons.api.format.ODataPubFormat; +import org.junit.Test; + +public class PropertyValueTestITCase extends AbstractTestITCase { + + @Test + public void retrieveIntPropertyValueTest() throws EdmPrimitiveTypeException { + final CommonURIBuilder<?> uriBuilder = client.getURIBuilder(testStaticServiceRootURL). + appendEntitySetSegment("People").appendKeySegment(5).appendPropertySegment("PersonID"). + appendValueSegment(); + final ODataValueRequest req = client.getRetrieveRequestFactory().getValueRequest(uriBuilder.build()); + req.setFormat(ODataValueFormat.TEXT); + assertEquals("5", req.execute().getBody().toString()); + } + + @Test + public void retrieveBooleanPropertyValueTest() throws EdmPrimitiveTypeException { + final CommonURIBuilder<?> uriBuilder = client.getURIBuilder(testStaticServiceRootURL). + appendEntitySetSegment("People").appendKeySegment(5).appendPropertySegment("IsRegistered"). + appendValueSegment(); + final ODataValueRequest req = client.getRetrieveRequestFactory().getValueRequest(uriBuilder.build()); + req.setFormat(ODataValueFormat.TEXT); + assertEquals("true", req.execute().getBody().toString()); + } + + @Test + public void retrieveStringPropertyValueTest() throws EdmPrimitiveTypeException { + final CommonURIBuilder<?> uriBuilder = client.getURIBuilder(testStaticServiceRootURL). + appendEntitySetSegment("People").appendKeySegment(5).appendPropertySegment("FirstName"). + appendValueSegment(); + final ODataValueRequest req = client.getRetrieveRequestFactory().getValueRequest(uriBuilder.build()); + req.setFormat(ODataValueFormat.TEXT); + assertEquals("Peter", req.execute().getBody().toString()); + } + + @Test + public void retrieveDatePropertyValueTest() { + final CommonURIBuilder<?> uriBuilder = client.getURIBuilder(testStaticServiceRootURL). + appendEntitySetSegment("Orders").appendKeySegment(8).appendPropertySegment("OrderDate"). + appendValueSegment(); + final ODataValueRequest req = client.getRetrieveRequestFactory().getValueRequest(uriBuilder.build()); + req.setFormat(ODataValueFormat.TEXT); + final ODataPrimitiveValue property = req.execute().getBody(); + assertEquals("2011-03-04T16:03:57Z", property.toString()); + } + + @Test + public void retrieveDecimalPropertyValueTest() throws EdmPrimitiveTypeException { + final CommonURIBuilder<?> uriBuilder = client.getURIBuilder(testStaticServiceRootURL). + appendEntitySetSegment("People").appendKeySegment(5).appendPropertySegment("Height"). + appendValueSegment(); + final ODataValueRequest req = client.getRetrieveRequestFactory().getValueRequest(uriBuilder.build()); + req.setFormat(ODataValueFormat.TEXT); + final ODataPrimitiveValue property = req.execute().getBody(); + assertEquals("179", property.toString()); + } + + @Test + public void retrieveBinaryPropertyValueTest() throws IOException { + final CommonURIBuilder<?> uriBuilder = client.getURIBuilder(testStaticServiceRootURL). + appendEntitySetSegment("People").appendKeySegment(5).appendPropertySegment("PDC"). + appendValueSegment(); + final ODataValueRequest req = client.getRetrieveRequestFactory().getValueRequest(uriBuilder.build()); + req.setFormat(ODataValueFormat.TEXT); + final ODataPrimitiveValue property = req.execute().getBody(); + assertEquals("fi653p3+MklA/LdoBlhWgnMTUUEo8tEgtbMXnF0a3CUNL9BZxXpSRiD9ebTnmNR0zWPjJ" + + "VIDx4tdmCnq55XrJh+RW9aI/b34wAogK3kcORw=", property.toString()); + } + + @Test(expected = ODataClientErrorException.class) + public void retrieveBinaryPropertyValueTestWithAtom() throws IOException { + final CommonURIBuilder<?> uriBuilder = client.getURIBuilder(testStaticServiceRootURL). + appendEntitySetSegment("People").appendKeySegment(5).appendPropertySegment("PDC"). + appendValueSegment(); + final ODataValueRequest req = client.getRetrieveRequestFactory().getValueRequest(uriBuilder.build()); + req.setAccept(ODataPubFormat.ATOM.toString(ODataServiceVersion.V40)); + req.execute().getBody(); + } + + @Test(expected = ODataClientErrorException.class) + public void retrieveBinaryPropertyValueTestWithXML() throws IOException { + final CommonURIBuilder<?> uriBuilder = client.getURIBuilder(testStaticServiceRootURL). + appendEntitySetSegment("People").appendKeySegment(5).appendPropertySegment("PDC"). + appendValueSegment(); + final ODataValueRequest req = client.getRetrieveRequestFactory().getValueRequest(uriBuilder.build()); + req.setAccept(ODataFormat.XML.toString()); + req.execute().getBody(); + } + + @Test + public void retrieveCollectionPropertyValueTest() { + CommonURIBuilder<?> uriBuilder = client.getURIBuilder(testStaticServiceRootURL). + appendEntitySetSegment("People").appendKeySegment(5).appendPropertySegment("Numbers"); + final ODataPropertyRequest req = client.getRetrieveRequestFactory().getPropertyRequest(uriBuilder.build()); + req.setFormat(ODataFormat.XML); + final ODataProperty property = req.execute().getBody(); + assertTrue(property.getValue().isCollection()); + assertEquals("555-555-5555", property.getCollectionValue().iterator().next().asPrimitive().toString()); + } + + @Test + public void retrieveNullPropertyValueTest() { + CommonURIBuilder<?> uriBuilder = client.getURIBuilder(testStaticServiceRootURL). + appendEntitySetSegment("People").appendKeySegment(5).appendPropertySegment("MiddleName"). + appendValueSegment(); + final ODataValueRequest req = client.getRetrieveRequestFactory().getValueRequest(uriBuilder.build()); + req.setFormat(ODataValueFormat.TEXT); + final ODataPrimitiveValue property = req.execute().getBody(); + assertTrue(StringUtils.isBlank(property.toString())); + } +} diff --git a/lib/client-core/src/test/java/org/apache/olingo/client/core/uri/v3/URIBuilderTest.java b/lib/client-core/src/test/java/org/apache/olingo/client/core/uri/v3/URIBuilderTest.java index 78badefd2..e949780e5 100644 --- a/lib/client-core/src/test/java/org/apache/olingo/client/core/uri/v3/URIBuilderTest.java +++ b/lib/client-core/src/test/java/org/apache/olingo/client/core/uri/v3/URIBuilderTest.java @@ -104,7 +104,7 @@ public class URIBuilderTest extends AbstractTest { public void unboundAction() throws URISyntaxException { final URIBuilder uriBuilder = getClient().getURIBuilder(SERVICE_ROOT). appendOperationCallSegment("ProductsByCategoryId", - Collections.<String, Object>singletonMap("categoryId", 2)); + Collections.<String, Object>singletonMap("categoryId", 2)); assertEquals(new org.apache.http.client.utils.URIBuilder( SERVICE_ROOT + "/ProductsByCategoryId(categoryId=2)").build(), uriBuilder.build()); @@ -128,4 +128,22 @@ public class URIBuilderTest extends AbstractTest { assertEquals(new org.apache.http.client.utils.URIBuilder( SERVICE_ROOT + "/Customers/Model/Namespace.VipCustomer(1)").build(), uriBuilder.build()); } + + @Test + public void expandMoreThenOnce() throws URISyntaxException { + URI uri = getClient().getURIBuilder(SERVICE_ROOT).appendEntitySetSegment("Products").appendKeySegment(5). + expand("Orders", "Customers").expand("Info").build(); + + assertEquals(new org.apache.http.client.utils.URIBuilder(SERVICE_ROOT + "/Products(5)"). + addParameter("$expand", "Orders,Customers,Info").build(), uri); + } + + @Test + public void selectMoreThenOnce() throws URISyntaxException { + URI uri = getClient().getURIBuilder(SERVICE_ROOT).appendEntitySetSegment("Customers").appendKeySegment(5). + select("Name", "Surname").expand("Info").select("Gender").build(); + + assertEquals(new org.apache.http.client.utils.URIBuilder(SERVICE_ROOT + "/Customers(5)"). + addParameter("$select", "Name,Surname,Gender").addParameter("$expand", "Info").build(), uri); + } } diff --git a/lib/client-core/src/test/java/org/apache/olingo/client/core/uri/v4/URIBuilderTest.java b/lib/client-core/src/test/java/org/apache/olingo/client/core/uri/v4/URIBuilderTest.java index 1518cf0fa..af26059e0 100644 --- a/lib/client-core/src/test/java/org/apache/olingo/client/core/uri/v4/URIBuilderTest.java +++ b/lib/client-core/src/test/java/org/apache/olingo/client/core/uri/v4/URIBuilderTest.java @@ -20,6 +20,7 @@ package org.apache.olingo.client.core.uri.v4; import java.net.URI; import java.net.URISyntaxException; +import java.util.LinkedHashMap; import org.apache.olingo.client.api.v4.ODataClient; import org.apache.olingo.client.api.uri.v4.URIBuilder; @@ -38,6 +39,22 @@ public class URIBuilderTest extends AbstractTest { return v4Client; } + @Test + public void expandWithOptions() throws URISyntaxException { + URI uri = getClient().getURIBuilder(SERVICE_ROOT).appendEntitySetSegment("Products").appendKeySegment(5). + expandWithOptions("ProductDetails", new LinkedHashMap<String, Object>() { + private static final long serialVersionUID = 3109256773218160485L; + + { + put("$expand", "ProductInfo"); + put("$select", "Price"); + } + }).expand("Orders", "Customers").build(); + + assertEquals(new org.apache.http.client.utils.URIBuilder(SERVICE_ROOT + "/Products(5)"). + addParameter("$expand", "ProductDetails($expand=ProductInfo,$select=Price),Orders,Customers").build(), uri); + } + @Test public void count() throws URISyntaxException { URI uri = getClient().getURIBuilder(SERVICE_ROOT).appendEntitySetSegment("Products").count().build(); @@ -132,5 +149,4 @@ public class URIBuilderTest extends AbstractTest { assertEquals(new org.apache.http.client.utils.URIBuilder( SERVICE_ROOT + "/Products").addParameter("$search", "blue OR green").build(), uriBuilder.build()); } - } diff --git a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/Constants.java b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/Constants.java index b4c048fff..3b5b9cc36 100644 --- a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/Constants.java +++ b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/Constants.java @@ -72,6 +72,8 @@ public interface Constants { public static final QName QNAME_ATTR_XML_BASE = new QName(XMLConstants.XML_NS_URI, ATTR_XML_BASE); + public static final String CONTEXT = "context"; + public static final String ATTR_REL = "rel"; public static final String ATTR_TITLE = "title"; @@ -173,7 +175,7 @@ public interface Constants { public final static String JSON_NULL = "odata.null"; - public final static String JSON_VALUE = "value"; + public final static String VALUE = "value"; public final static String JSON_URL = "url"; @@ -192,6 +194,12 @@ public interface Constants { // Atom stuff public final static String ATOM_ELEM_ENTRY = "entry"; + public final static String ATOM_ELEM_ENTRY_REF = "ref"; + + public final static String ATOM_ELEM_ENTRY_REF_ID = "id"; + + public final static QName QNAME_ATOM_ELEM_ENTRY_REF_ID = new QName(ATOM_ELEM_ENTRY_REF_ID); + public static final QName QNAME_ATOM_ELEM_ENTRY = new QName(NS_ATOM, ATOM_ELEM_ENTRY); public final static String ATOM_ELEM_FEED = "feed"; diff --git a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/Entry.java b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/Entry.java index ea73b7dfc..5ac0dbd86 100644 --- a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/Entry.java +++ b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/Entry.java @@ -38,6 +38,21 @@ public interface Entry { */ URI getBaseURI(); + /** + * The context URL describes the content of the payload. It consists of the canonical metadata document URL and a + * fragment identifying the relevant portion of the metadata document. + * + * @return context URL. + */ + URI getContextURL(); + + /** + * Set context URL. + * + * @param contextURL context URL. + */ + void setContextURL(final URI contextURL); + /** * Gets entry type. * @@ -53,12 +68,19 @@ public interface Entry { void setType(String type); /** - * Gest entry ID. + * Gets entry ID. * * @return entry ID. */ String getId(); + /** + * Sets entry ID. + * + * @param id entry ID. + */ + void setId(String id); + /** * Gets entry self link. * diff --git a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/Feed.java b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/Feed.java index 4e98ac603..45b28d9ac 100644 --- a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/Feed.java +++ b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/Feed.java @@ -30,6 +30,21 @@ public interface Feed { */ URI getBaseURI(); + /** + * The context URL describes the content of the payload. It consists of the canonical metadata document URL and a + * fragment identifying the relevant portion of the metadata document. + * + * @return context URL. + */ + URI getContextURL(); + + /** + * Set context URL. + * + * @param contextURL context URL. + */ + void setContextURL(final URI contextURL); + /** * Sets number of entries. * @@ -71,5 +86,4 @@ public interface Feed { * @param next next link. */ void setNext(URI next); - } diff --git a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/Property.java b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/Property.java index feb8f71a7..da34c169e 100644 --- a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/Property.java +++ b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/Property.java @@ -18,8 +18,18 @@ */ package org.apache.olingo.commons.api.data; +import java.net.URI; + public interface Property { + /** + * The context URL describes the content of the payload. It consists of the canonical metadata document URL and a + * fragment identifying the relevant portion of the metadata document. + * + * @return context URL. + */ + URI getContextURL(); + String getName(); void setName(String name); diff --git a/lib/client-api/src/main/java/org/apache/olingo/client/api/communication/header/ODataHeaderValues.java b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/domain/AbstractODataPayload.java similarity index 54% rename from lib/client-api/src/main/java/org/apache/olingo/client/api/communication/header/ODataHeaderValues.java rename to lib/commons-api/src/main/java/org/apache/olingo/commons/api/domain/AbstractODataPayload.java index 37c4698e6..9bc177cea 100644 --- a/lib/client-api/src/main/java/org/apache/olingo/client/api/communication/header/ODataHeaderValues.java +++ b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/domain/AbstractODataPayload.java @@ -16,30 +16,37 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.olingo.client.api.communication.header; +package org.apache.olingo.commons.api.domain; + +import java.net.URI; /** - * Constant header values class. + * OData entity. */ -public class ODataHeaderValues { +public abstract class AbstractODataPayload extends ODataItem { + + private static final long serialVersionUID = -8234709365887433612L; /** - * <code>Prefer</code> header, return content. + * Context URL. + */ + private URI contextURL; + + public AbstractODataPayload(final String name) { + super(name); + } + + /** + * The context URL describes the content of the payload. It consists of the canonical metadata document URL and a + * fragment identifying the relevant portion of the metadata document. * - * @see ODataHeaders.HeaderName#prefer + * @return context URL. */ - public static final String preferReturnContent = "return-content"; - - /** - * <code>Prefer</code> header, return no content. - * - * @see ODataHeaders.HeaderName#prefer - */ - public static final String preferReturnNoContent = "return-no-content"; - - /** - * @see ODataHeaders.HeaderName#dataServiceUrlConventions - */ - public static final String keyAsSegment = "KeyAsSegment"; + public URI getContextURL() { + return contextURL; + } + public void setContextURL(final URI contextURL) { + this.contextURL = contextURL; + } } diff --git a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/domain/ODataEntity.java b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/domain/ODataEntity.java index 6f84e4bf7..a5826d193 100644 --- a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/domain/ODataEntity.java +++ b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/domain/ODataEntity.java @@ -26,10 +26,15 @@ import org.apache.commons.lang3.StringUtils; /** * OData entity. */ -public class ODataEntity extends ODataItem implements ODataInvokeResult { +public class ODataEntity extends AbstractODataPayload implements ODataInvokeResult { private static final long serialVersionUID = 8360640095932811034L; + /** + * Entity reference. + */ + private String reference; + /** * ETag. */ @@ -89,6 +94,30 @@ public class ODataEntity extends ODataItem implements ODataInvokeResult { super(name); } + /** + * To request entity references in place of the actual entities, the client issues a GET request with /$ref appended + * to the resource path. + * <br /> + * If the resource path does not identify an entity or a collection of entities, the service returns 404 Not Found. + * <br /> + * If the resource path terminates on a collection, the response MUST be the format-specific representation of a + * collection of entity references pointing to the related entities. If no entities are related, the response is the + * format-specific representation of an empty collection. + * <br /> + * If the resource path terminates on a single entity, the response MUST be the format-specific representation of an + * entity reference pointing to the related single entity. If the resource path terminates on a single entity and no + * such entity exists, the service returns 404 Not Found. + * + * @return entity reference. + */ + public String getReference() { + return reference; + } + + public void setReference(final String reference) { + this.reference = reference; + } + /** * Gets ETag. * diff --git a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/domain/ODataEntitySet.java b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/domain/ODataEntitySet.java index d3b146d99..083b7adce 100644 --- a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/domain/ODataEntitySet.java +++ b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/domain/ODataEntitySet.java @@ -25,7 +25,7 @@ import java.util.List; /** * OData entity collection. If pagination was used to get this instance, forward page navigation URI will be available. */ -public class ODataEntitySet extends ODataItem implements ODataInvokeResult { +public class ODataEntitySet extends AbstractODataPayload implements ODataInvokeResult { private static final long serialVersionUID = 9039605899821494024L; diff --git a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/domain/ODataItem.java b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/domain/ODataItem.java index b40eeb785..6ab2376d0 100644 --- a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/domain/ODataItem.java +++ b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/domain/ODataItem.java @@ -39,16 +39,16 @@ public abstract class ODataItem implements Serializable { */ protected static final Logger LOG = LoggerFactory.getLogger(ODataItem.class); - /** - * OData item self link. - */ - protected URI link; - /** * OData entity name/type. */ private final String name; + /** + * OData item self link. + */ + protected URI link; + /** * Constructor. * @@ -58,6 +58,15 @@ public abstract class ODataItem implements Serializable { this.name = name; } + /** + * Returns OData entity name. + * + * @return entity name. + */ + public String getName() { + return name; + } + /** * Returns self link. * @@ -76,15 +85,6 @@ public abstract class ODataItem implements Serializable { this.link = link; } - /** - * Returns OData entity name. - * - * @return entity name. - */ - public String getName() { - return name; - } - /** * {@inheritDoc } */ diff --git a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/domain/ODataLink.java b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/domain/ODataLink.java index 9e6845427..2735f994d 100644 --- a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/domain/ODataLink.java +++ b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/domain/ODataLink.java @@ -98,7 +98,6 @@ public class ODataLink extends ODataItem { return uri.normalize(); } - /** * Link type. */ @@ -186,5 +185,4 @@ public class ODataLink extends ODataItem { public String getMediaETag() { return mediaETag; } - } diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AbstractAtomDealer.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AbstractAtomDealer.java index 940d8a539..d4cf40a85 100644 --- a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AbstractAtomDealer.java +++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AbstractAtomDealer.java @@ -49,8 +49,15 @@ abstract class AbstractAtomDealer { protected final QName countQName; protected final QName uriQName; + protected final QName nextQName; + protected final QName contextQName; + + protected final QName entityRefQName; + + protected final QName v4PropertyValueQName; + public AbstractAtomDealer(final ODataServiceVersion version) { this.version = version; @@ -72,6 +79,12 @@ abstract class AbstractAtomDealer { new QName(version.getNamespaceMap().get(ODataServiceVersion.NS_DATASERVICES), Constants.ELEM_URI); this.nextQName = new QName(version.getNamespaceMap().get(ODataServiceVersion.NS_DATASERVICES), Constants.NEXT_LINK_REL); + this.contextQName = + new QName(version.getNamespaceMap().get(ODataServiceVersion.NS_METADATA), Constants.CONTEXT); + this.entityRefQName = + new QName(version.getNamespaceMap().get(ODataServiceVersion.NS_METADATA), Constants.ATOM_ELEM_ENTRY_REF); + this.v4PropertyValueQName = + new QName(version.getNamespaceMap().get(ODataServiceVersion.NS_METADATA), Constants.VALUE); } protected void namespaces(final XMLStreamWriter writer) throws XMLStreamException { @@ -83,5 +96,4 @@ abstract class AbstractAtomDealer { writer.writeNamespace(Constants.PREFIX_GML, Constants.NS_GML); writer.writeNamespace(Constants.PREFIX_GEORSS, Constants.NS_GEORSS); } - } diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AbstractAtomObject.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AbstractAtomObject.java index f09de400e..2436e3e68 100644 --- a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AbstractAtomObject.java +++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AbstractAtomObject.java @@ -31,6 +31,8 @@ abstract class AbstractAtomObject extends AbstractPayloadObject { private URI baseURI; + private URI contextURL; + private String id; private String title; @@ -47,10 +49,28 @@ abstract class AbstractAtomObject extends AbstractPayloadObject { this.baseURI = URI.create(baseURI); } + /** + * The context URL describes the content of the payload. It consists of the canonical metadata document URL and a + * fragment identifying the relevant portion of the metadata document. + * + * @return context URL. + */ + public URI getContextURL() { + return contextURL; + } + + public void setContextURL(final URI contextURL) { + this.contextURL = contextURL; + } + public String getId() { return id; } + public void setId(final String id) { + this.id = id; + } + public String getTitle() { return title; } @@ -74,5 +94,4 @@ abstract class AbstractAtomObject extends AbstractPayloadObject { this.updated = ISO_DATEFORMAT.parse(value); } } - } diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AbstractPropertyImpl.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AbstractPropertyImpl.java index e005f98fe..b3ca45906 100644 --- a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AbstractPropertyImpl.java +++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AbstractPropertyImpl.java @@ -18,6 +18,7 @@ */ package org.apache.olingo.commons.core.data; +import java.net.URI; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ReflectionToStringBuilder; @@ -27,12 +28,29 @@ import org.apache.olingo.commons.api.data.Value; public abstract class AbstractPropertyImpl implements Property { + private URI contextURL; + private String name; private String type; private Value value; + /** + * The context URL describes the content of the payload. It consists of the canonical metadata document URL and a + * fragment identifying the relevant portion of the metadata document. + * + * @return context URL. + */ + @Override + public URI getContextURL() { + return contextURL; + } + + public void setContextURL(final URI contextURL) { + this.contextURL = contextURL; + } + @Override public String getName() { return name; diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AtomDeserializer.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AtomDeserializer.java index b8775d5be..9a4eac1ab 100644 --- a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AtomDeserializer.java +++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AtomDeserializer.java @@ -168,112 +168,135 @@ public class AtomDeserializer extends AbstractAtomDealer { } private AtomEntryImpl entry(final XMLEventReader reader, final StartElement start) throws XMLStreamException { - if (!Constants.QNAME_ATOM_ELEM_ENTRY.equals(start.getName())) { - return null; - } + final AtomEntryImpl entry; + if (entityRefQName.equals(start.getName())) { + entry = entryRef(start); + } else if (Constants.QNAME_ATOM_ELEM_ENTRY.equals(start.getName())) { + entry = new AtomEntryImpl(); + final Attribute xmlBase = start.getAttributeByName(Constants.QNAME_ATTR_XML_BASE); + if (xmlBase != null) { + entry.setBaseURI(xmlBase.getValue()); + } - final AtomEntryImpl entry = new AtomEntryImpl(); - final Attribute xmlBase = start.getAttributeByName(Constants.QNAME_ATTR_XML_BASE); - if (xmlBase != null) { - entry.setBaseURI(xmlBase.getValue()); - } - final Attribute etag = start.getAttributeByName(etagQName); - if (etag != null) { - entry.setETag(etag.getValue()); - } + entry.setContextURL(retrieveContextURL(start, entry.getBaseURI())); - boolean foundEndEntry = false; - while (reader.hasNext() && !foundEndEntry) { - final XMLEvent event = reader.nextEvent(); + final Attribute etag = start.getAttributeByName(etagQName); + if (etag != null) { + entry.setETag(etag.getValue()); + } - if (event.isStartElement()) { - if (Constants.QNAME_ATOM_ELEM_ID.equals(event.asStartElement().getName())) { - common(reader, event.asStartElement(), entry, "id"); - } else if (Constants.QNAME_ATOM_ELEM_TITLE.equals(event.asStartElement().getName())) { - common(reader, event.asStartElement(), entry, "title"); - } else if (Constants.QNAME_ATOM_ELEM_SUMMARY.equals(event.asStartElement().getName())) { - common(reader, event.asStartElement(), entry, "summary"); - } else if (Constants.QNAME_ATOM_ELEM_UPDATED.equals(event.asStartElement().getName())) { - common(reader, event.asStartElement(), entry, "updated"); - } else if (Constants.QNAME_ATOM_ELEM_CATEGORY.equals(event.asStartElement().getName())) { - final Attribute term = event.asStartElement().getAttributeByName(QName.valueOf(Constants.ATOM_ATTR_TERM)); - if (term != null) { - entry.setType(term.getValue()); - } - } else if (Constants.QNAME_ATOM_ELEM_LINK.equals(event.asStartElement().getName())) { - final LinkImpl link = new LinkImpl(); - final Attribute rel = event.asStartElement().getAttributeByName(QName.valueOf(Constants.ATTR_REL)); - if (rel != null) { - link.setRel(rel.getValue()); - } - final Attribute title = event.asStartElement().getAttributeByName(QName.valueOf(Constants.ATTR_TITLE)); - if (title != null) { - link.setTitle(title.getValue()); - } - final Attribute href = event.asStartElement().getAttributeByName(QName.valueOf(Constants.ATTR_HREF)); - if (href != null) { - link.setHref(href.getValue()); - } - final Attribute type = event.asStartElement().getAttributeByName(QName.valueOf(Constants.ATTR_TYPE)); - if (type != null) { - link.setType(type.getValue()); - } + boolean foundEndEntry = false; + while (reader.hasNext() && !foundEndEntry) { + final XMLEvent event = reader.nextEvent(); - if (Constants.SELF_LINK_REL.equals(link.getRel())) { - entry.setSelfLink(link); - } else if (Constants.EDIT_LINK_REL.equals(link.getRel())) { - entry.setEditLink(link); - } else if (link.getRel().startsWith(version.getNamespaceMap().get(ODataServiceVersion.NAVIGATION_LINK_REL))) { - entry.getNavigationLinks().add(link); - inline(reader, event.asStartElement(), link); - } else if (link.getRel().startsWith( - version.getNamespaceMap().get(ODataServiceVersion.ASSOCIATION_LINK_REL))) { - - entry.getAssociationLinks().add(link); - } else if (link.getRel().startsWith( - version.getNamespaceMap().get(ODataServiceVersion.MEDIA_EDIT_LINK_REL))) { - - final Attribute metag = event.asStartElement().getAttributeByName(etagQName); - if (metag != null) { - link.setMediaETag(metag.getValue()); + if (event.isStartElement()) { + if (Constants.QNAME_ATOM_ELEM_ID.equals(event.asStartElement().getName())) { + common(reader, event.asStartElement(), entry, "id"); + } else if (Constants.QNAME_ATOM_ELEM_TITLE.equals(event.asStartElement().getName())) { + common(reader, event.asStartElement(), entry, "title"); + } else if (Constants.QNAME_ATOM_ELEM_SUMMARY.equals(event.asStartElement().getName())) { + common(reader, event.asStartElement(), entry, "summary"); + } else if (Constants.QNAME_ATOM_ELEM_UPDATED.equals(event.asStartElement().getName())) { + common(reader, event.asStartElement(), entry, "updated"); + } else if (Constants.QNAME_ATOM_ELEM_CATEGORY.equals(event.asStartElement().getName())) { + final Attribute term = event.asStartElement().getAttributeByName(QName.valueOf(Constants.ATOM_ATTR_TERM)); + if (term != null) { + entry.setType(term.getValue()); } - entry.getMediaEditLinks().add(link); - } - } else if (actionQName.equals(event.asStartElement().getName())) { - final ODataOperation operation = new ODataOperation(); - final Attribute metadata = event.asStartElement().getAttributeByName(QName.valueOf(Constants.ATTR_METADATA)); - if (metadata != null) { - operation.setMetadataAnchor(metadata.getValue()); - } - final Attribute title = event.asStartElement().getAttributeByName(QName.valueOf(Constants.ATTR_TITLE)); - if (title != null) { - operation.setTitle(title.getValue()); - } - final Attribute target = event.asStartElement().getAttributeByName(QName.valueOf(Constants.ATTR_TARGET)); - if (target != null) { - operation.setTarget(URI.create(target.getValue())); - } - - entry.getOperations().add(operation); - } else if (Constants.QNAME_ATOM_ELEM_CONTENT.equals(event.asStartElement().getName())) { - final Attribute type = event.asStartElement().getAttributeByName(QName.valueOf(Constants.ATTR_TYPE)); - if (type == null || ContentType.APPLICATION_XML.equals(type.getValue())) { - properties(reader, skipBeforeFirstStartElement(reader), entry); - } else { - entry.setMediaContentType(type.getValue()); - final Attribute src = event.asStartElement().getAttributeByName(QName.valueOf(Constants.ATOM_ATTR_SRC)); - if (src != null) { - entry.setMediaContentSource(src.getValue()); + } else if (Constants.QNAME_ATOM_ELEM_LINK.equals(event.asStartElement().getName())) { + final LinkImpl link = new LinkImpl(); + final Attribute rel = event.asStartElement().getAttributeByName(QName.valueOf(Constants.ATTR_REL)); + if (rel != null) { + link.setRel(rel.getValue()); } + final Attribute title = event.asStartElement().getAttributeByName(QName.valueOf(Constants.ATTR_TITLE)); + if (title != null) { + link.setTitle(title.getValue()); + } + final Attribute href = event.asStartElement().getAttributeByName(QName.valueOf(Constants.ATTR_HREF)); + if (href != null) { + link.setHref(href.getValue()); + } + final Attribute type = event.asStartElement().getAttributeByName(QName.valueOf(Constants.ATTR_TYPE)); + if (type != null) { + link.setType(type.getValue()); + } + + if (Constants.SELF_LINK_REL.equals(link.getRel())) { + entry.setSelfLink(link); + } else if (Constants.EDIT_LINK_REL.equals(link.getRel())) { + entry.setEditLink(link); + } else if (link.getRel().startsWith( + version.getNamespaceMap().get(ODataServiceVersion.NAVIGATION_LINK_REL))) { + entry.getNavigationLinks().add(link); + inline(reader, event.asStartElement(), link); + } else if (link.getRel().startsWith( + version.getNamespaceMap().get(ODataServiceVersion.ASSOCIATION_LINK_REL))) { + + entry.getAssociationLinks().add(link); + } else if (link.getRel().startsWith( + version.getNamespaceMap().get(ODataServiceVersion.MEDIA_EDIT_LINK_REL))) { + + final Attribute metag = event.asStartElement().getAttributeByName(etagQName); + if (metag != null) { + link.setMediaETag(metag.getValue()); + } + entry.getMediaEditLinks().add(link); + } + } else if (actionQName.equals(event.asStartElement().getName())) { + final ODataOperation operation = new ODataOperation(); + final Attribute metadata = + event.asStartElement().getAttributeByName(QName.valueOf(Constants.ATTR_METADATA)); + if (metadata != null) { + operation.setMetadataAnchor(metadata.getValue()); + } + final Attribute title = event.asStartElement().getAttributeByName(QName.valueOf(Constants.ATTR_TITLE)); + if (title != null) { + operation.setTitle(title.getValue()); + } + final Attribute target = event.asStartElement().getAttributeByName(QName.valueOf(Constants.ATTR_TARGET)); + if (target != null) { + operation.setTarget(URI.create(target.getValue())); + } + + entry.getOperations().add(operation); + } else if (Constants.QNAME_ATOM_ELEM_CONTENT.equals(event.asStartElement().getName())) { + final Attribute type = event.asStartElement().getAttributeByName(QName.valueOf(Constants.ATTR_TYPE)); + if (type == null || ContentType.APPLICATION_XML.equals(type.getValue())) { + properties(reader, skipBeforeFirstStartElement(reader), entry); + } else { + entry.setMediaContentType(type.getValue()); + final Attribute src = event.asStartElement().getAttributeByName(QName.valueOf(Constants.ATOM_ATTR_SRC)); + if (src != null) { + entry.setMediaContentSource(src.getValue()); + } + } + } else if (propertiesQName.equals(event.asStartElement().getName())) { + properties(reader, event.asStartElement(), entry); } - } else if (propertiesQName.equals(event.asStartElement().getName())) { - properties(reader, event.asStartElement(), entry); + } + + if (event.isEndElement() && start.getName().equals(event.asEndElement().getName())) { + foundEndEntry = true; } } - if (event.isEndElement() && start.getName().equals(event.asEndElement().getName())) { - foundEndEntry = true; - } + return entry; + } else { + entry = null; + } + + return entry; + } + + private AtomEntryImpl entryRef(final StartElement start) throws XMLStreamException { + final AtomEntryImpl entry = new AtomEntryImpl(); + entry.setContextURL(retrieveContextURL(start, null)); + + final Attribute entryRefId = start.getAttributeByName(Constants.QNAME_ATOM_ELEM_ENTRY_REF_ID); + + if (entryRefId != null) { + entry.setId(entryRefId.getValue()); } return entry; @@ -312,6 +335,8 @@ public class AtomDeserializer extends AbstractAtomDealer { feed.setBaseURI(xmlBase.getValue()); } + feed.setContextURL(retrieveContextURL(start, feed.getBaseURI())); + boolean foundEndFeed = false; while (reader.hasNext() && !foundEndFeed) { final XMLEvent event = reader.nextEvent(); @@ -365,4 +390,16 @@ public class AtomDeserializer extends AbstractAtomDealer { } return null; } + + private URI retrieveContextURL(final StartElement start, final URI base) { + final Attribute context = start.getAttributeByName(contextQName); + + if (context == null) { + return base == null + ? null + : URI.create(base.toASCIIString() + "/" + Constants.METADATA); + } else { + return URI.create(context.getValue()); + } + } } diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AtomPropertyDeserializer.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AtomPropertyDeserializer.java index 91ad49add..fd466ea9d 100644 --- a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AtomPropertyDeserializer.java +++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AtomPropertyDeserializer.java @@ -18,6 +18,8 @@ */ package org.apache.olingo.commons.core.data; +import java.net.URI; +import javax.xml.namespace.QName; import javax.xml.stream.XMLEventReader; import javax.xml.stream.XMLStreamException; import javax.xml.stream.events.Attribute; @@ -163,17 +165,29 @@ class AtomPropertyDeserializer extends AbstractAtomDealer { public AtomPropertyImpl deserialize(final XMLEventReader reader, final StartElement start) throws XMLStreamException { - final AtomPropertyImpl property = new AtomPropertyImpl(); - property.setName(start.getName().getLocalPart()); - final Attribute typeAttr = start.getAttributeByName(this.typeQName); + final Attribute context = start.getAttributeByName(contextQName); + + property.setContextURL(context == null ? null : URI.create(context.getValue())); + + final QName name = start.getName(); + + if (ODataServiceVersion.V40 == version && v4PropertyValueQName.equals(name)) { + // retrieve name from context + final String contextURL = property.getContextURL().toASCIIString(); + property.setName(contextURL.substring(contextURL.lastIndexOf("/") + 1)); + } else { + property.setName(name.getLocalPart()); + } + + final Attribute nullAttr = start.getAttributeByName(this.nullQName); Value value; - final Attribute nullAttr = start.getAttributeByName(this.nullQName); - final String typeAttrValue = typeAttr == null ? null : typeAttr.getValue(); - if (nullAttr == null) { + final Attribute typeAttr = start.getAttributeByName(this.typeQName); + final String typeAttrValue = typeAttr == null ? null : typeAttr.getValue(); + final EdmTypeInfo typeInfo = StringUtils.isBlank(typeAttrValue) ? null : new EdmTypeInfo.Builder().setTypeExpression(typeAttrValue).build(); diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AtomPropertySerializer.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AtomPropertySerializer.java index 8d2a8e1ea..412a127a0 100644 --- a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AtomPropertySerializer.java +++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AtomPropertySerializer.java @@ -64,6 +64,14 @@ class AtomPropertySerializer extends AbstractAtomDealer { writer.writeStartElement(Constants.PREFIX_DATASERVICES, property.getName(), version.getNamespaceMap().get(ODataServiceVersion.NS_DATASERVICES)); + + if (version == ODataServiceVersion.V40 && property.getContextURL() != null) { + writer.writeAttribute( + version.getNamespaceMap().get(ODataServiceVersion.NS_METADATA), + Constants.CONTEXT, + property.getContextURL().toASCIIString()); + } + if (standalone) { namespaces(writer); } diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AtomSerializer.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AtomSerializer.java index 4c6fb3a52..8bab108ff 100644 --- a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AtomSerializer.java +++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AtomSerializer.java @@ -127,6 +127,13 @@ public class AtomSerializer extends AbstractAtomDealer { } private void entry(final XMLStreamWriter writer, final Entry entry) throws XMLStreamException { + if (version == ODataServiceVersion.V40 && entry.getContextURL() != null) { + writer.writeAttribute( + version.getNamespaceMap().get(ODataServiceVersion.NS_METADATA), + Constants.CONTEXT, + entry.getContextURL().toASCIIString()); + } + if (entry.getBaseURI() != null) { writer.writeAttribute(XMLConstants.XML_NS_URI, Constants.ATTR_XML_BASE, entry.getBaseURI().toASCIIString()); } @@ -184,6 +191,13 @@ public class AtomSerializer extends AbstractAtomDealer { } private void feed(final XMLStreamWriter writer, final Feed feed) throws XMLStreamException { + if (version == ODataServiceVersion.V40 && feed.getContextURL() != null) { + writer.writeAttribute( + version.getNamespaceMap().get(ODataServiceVersion.NS_METADATA), + Constants.CONTEXT, + feed.getContextURL().toASCIIString()); + } + if (feed.getBaseURI() != null) { writer.writeAttribute(XMLConstants.XML_NS_URI, Constants.ATTR_XML_BASE, feed.getBaseURI().toASCIIString()); } diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONEntryDeserializer.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONEntryDeserializer.java index 3647fac5e..e87daa0e9 100644 --- a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONEntryDeserializer.java +++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONEntryDeserializer.java @@ -23,13 +23,11 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.ObjectCodec; import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import java.io.IOException; import java.net.URI; -import java.text.ParseException; import java.util.HashSet; import java.util.Iterator; import java.util.Map; @@ -84,14 +82,17 @@ public class JSONEntryDeserializer extends AbstractJsonDeserializer<JSONEntryImp final ObjectNode tree = (ObjectNode) parser.getCodec().readTree(parser); - if (tree.has(Constants.JSON_VALUE) && tree.get(Constants.JSON_VALUE).isArray()) { + if (tree.has(Constants.VALUE) && tree.get(Constants.VALUE).isArray()) { throw new JsonParseException("Expected OData Entity, found EntitySet", parser.getCurrentLocation()); } final JSONEntryImpl entry = new JSONEntryImpl(); - if (tree.hasNonNull(Constants.JSON_METADATA)) { - entry.setMetadata(URI.create(tree.get(Constants.JSON_METADATA).textValue())); + if (tree.hasNonNull(Constants.JSON_CONTEXT)) { + entry.setContextURL(URI.create(tree.get(Constants.JSON_CONTEXT).textValue())); + tree.remove(Constants.JSON_CONTEXT); + } else if (tree.hasNonNull(Constants.JSON_METADATA)) { + entry.setContextURL(URI.create(tree.get(Constants.JSON_METADATA).textValue())); tree.remove(Constants.JSON_METADATA); } @@ -111,11 +112,7 @@ public class JSONEntryDeserializer extends AbstractJsonDeserializer<JSONEntryImp } if (tree.hasNonNull(Constants.JSON_ID)) { - try { - entry.setId(tree.get(Constants.JSON_ID).textValue()); - } catch (ParseException e) { - throw new JsonMappingException("While parsing Atom entry or feed common elements", e); - } + entry.setId(tree.get(Constants.JSON_ID).textValue()); tree.remove(Constants.JSON_ID); } diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONEntryImpl.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONEntryImpl.java index 765c18a20..10123d452 100644 --- a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONEntryImpl.java +++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONEntryImpl.java @@ -21,7 +21,6 @@ package org.apache.olingo.commons.core.data; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import java.net.URI; -import java.text.ParseException; import org.apache.olingo.commons.api.Constants; /** @@ -33,43 +32,19 @@ public class JSONEntryImpl extends AbstractEntry { private static final long serialVersionUID = -5275365545400797758L; - private URI metadata; - private String mediaETag; - public void setId(final String id) throws ParseException { - this.setCommonProperty("id", id); - } - @Override public URI getBaseURI() { URI baseURI = null; - if (metadata != null) { - final String metadataURI = getMetadata().toASCIIString(); + if (getContextURL() != null) { + final String metadataURI = getContextURL().toASCIIString(); baseURI = URI.create(metadataURI.substring(0, metadataURI.indexOf(Constants.METADATA))); } return baseURI; } - /** - * Gets the metadata URI. - * - * @return the metadata URI - */ - public URI getMetadata() { - return metadata; - } - - /** - * Sets the metadata URI. - * - * @param metadata metadata URI. - */ - public void setMetadata(final URI metadata) { - this.metadata = metadata; - } - /** * The odata.mediaEtag annotation MAY be included; its value MUST be the ETag of the binary stream represented by this * media entity or named stream property. diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONEntrySerializer.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONEntrySerializer.java index 404174811..bd5a145c6 100644 --- a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONEntrySerializer.java +++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONEntrySerializer.java @@ -32,6 +32,7 @@ import org.apache.olingo.commons.api.data.Entry; import org.apache.olingo.commons.api.data.Link; import org.apache.olingo.commons.api.data.Property; import org.apache.olingo.commons.api.domain.ODataLinkType; +import org.apache.olingo.commons.api.edm.constants.ODataServiceVersion; /** * Writes out JSON string from an entry. @@ -44,9 +45,12 @@ public class JSONEntrySerializer extends AbstractJsonSerializer<JSONEntryImpl> { jgen.writeStartObject(); - if (entry.getMetadata() != null) { - jgen.writeStringField(Constants.JSON_METADATA, entry.getMetadata().toASCIIString()); + if (entry.getContextURL() != null) { + jgen.writeStringField( + version == ODataServiceVersion.V40 ? Constants.JSON_CONTEXT : Constants.JSON_METADATA, + entry.getContextURL().toASCIIString()); } + if (entry.getId() != null) { jgen.writeStringField(Constants.JSON_ID, entry.getId()); } diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONFeedDeserializer.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONFeedDeserializer.java index 81a8d473b..d278a3471 100644 --- a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONFeedDeserializer.java +++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONFeedDeserializer.java @@ -41,15 +41,20 @@ public class JSONFeedDeserializer extends AbstractJsonDeserializer<JSONFeedImpl> final ObjectNode tree = (ObjectNode) parser.getCodec().readTree(parser); - if (!tree.has(Constants.JSON_VALUE)) { + if (!tree.has(Constants.VALUE)) { return null; } final JSONFeedImpl feed = new JSONFeedImpl(); - if (tree.hasNonNull(Constants.JSON_METADATA)) { - feed.setMetadata(URI.create(tree.get(Constants.JSON_METADATA).textValue())); + if (tree.hasNonNull(Constants.JSON_CONTEXT)) { + feed.setContextURL(URI.create(tree.get(Constants.JSON_CONTEXT).textValue())); + tree.remove(Constants.JSON_CONTEXT); + } else if (tree.hasNonNull(Constants.JSON_METADATA)) { + feed.setContextURL(URI.create(tree.get(Constants.JSON_METADATA).textValue())); + tree.remove(Constants.JSON_METADATA); } + if (tree.hasNonNull(Constants.JSON_COUNT)) { feed.setCount(tree.get(Constants.JSON_COUNT).asInt()); } @@ -57,8 +62,8 @@ public class JSONFeedDeserializer extends AbstractJsonDeserializer<JSONFeedImpl> feed.setNext(URI.create(tree.get(Constants.JSON_NEXT_LINK).textValue())); } - if (tree.hasNonNull(Constants.JSON_VALUE)) { - for (final Iterator<JsonNode> itor = tree.get(Constants.JSON_VALUE).iterator(); itor.hasNext();) { + if (tree.hasNonNull(Constants.VALUE)) { + for (final Iterator<JsonNode> itor = tree.get(Constants.VALUE).iterator(); itor.hasNext();) { feed.getEntries().add(itor.next().traverse(parser.getCodec()).readValueAs(JSONEntryImpl.class)); } } diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONFeedImpl.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONFeedImpl.java index f41e21c24..e89bb1f6b 100644 --- a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONFeedImpl.java +++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONFeedImpl.java @@ -38,9 +38,9 @@ public class JSONFeedImpl extends AbstractPayloadObject implements Feed { private static final long serialVersionUID = -3576372289800799417L; - private String id; + private URI contextURL; - private URI metadata; + private String id; private Integer count; @@ -51,8 +51,8 @@ public class JSONFeedImpl extends AbstractPayloadObject implements Feed { @Override public URI getBaseURI() { URI baseURI = null; - if (metadata != null) { - final String metadataURI = getMetadata().toASCIIString(); + if (getContextURL() != null) { + final String metadataURI = getContextURL().toASCIIString(); baseURI = URI.create(metadataURI.substring(0, metadataURI.indexOf(Constants.METADATA))); } @@ -60,21 +60,15 @@ public class JSONFeedImpl extends AbstractPayloadObject implements Feed { } /** - * Gets the metadata URI. - * - * @return the metadata URI + * {@inheritDoc} */ - public URI getMetadata() { - return metadata; + @Override + public URI getContextURL() { + return contextURL; } - /** - * Sets the metadata URI. - * - * @param metadata metadata URI. - */ - public void setMetadata(final URI metadata) { - this.metadata = metadata; + public void setContextURL(final URI context) { + this.contextURL = context; } @Override diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONFeedSerializer.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONFeedSerializer.java index 25a735858..df359b888 100644 --- a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONFeedSerializer.java +++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONFeedSerializer.java @@ -24,6 +24,7 @@ import com.fasterxml.jackson.databind.SerializerProvider; import java.io.IOException; import org.apache.olingo.commons.api.Constants; import org.apache.olingo.commons.api.data.Entry; +import org.apache.olingo.commons.api.edm.constants.ODataServiceVersion; public class JSONFeedSerializer extends AbstractJsonSerializer<JSONFeedImpl> { @@ -33,8 +34,10 @@ public class JSONFeedSerializer extends AbstractJsonSerializer<JSONFeedImpl> { jgen.writeStartObject(); - if (feed.getMetadata() != null) { - jgen.writeStringField(Constants.JSON_METADATA, feed.getMetadata().toASCIIString()); + if (feed.getContextURL() != null) { + jgen.writeStringField( + version == ODataServiceVersion.V40 ? Constants.JSON_CONTEXT : Constants.JSON_METADATA, + feed.getContextURL().toASCIIString()); } if (feed.getId() != null) { jgen.writeStringField(Constants.JSON_ID, feed.getId()); @@ -46,12 +49,11 @@ public class JSONFeedSerializer extends AbstractJsonSerializer<JSONFeedImpl> { jgen.writeStringField(Constants.JSON_NEXT_LINK, feed.getNext().toASCIIString()); } - jgen.writeArrayFieldStart(Constants.JSON_VALUE); + jgen.writeArrayFieldStart(Constants.VALUE); for (Entry entry : feed.getEntries()) { jgen.writeObject(entry); } jgen.writeEndArray(); } - } diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONPropertyDeserializer.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONPropertyDeserializer.java index b68d99856..8af23e1ed 100644 --- a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONPropertyDeserializer.java +++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONPropertyDeserializer.java @@ -41,16 +41,19 @@ public class JSONPropertyDeserializer extends AbstractJsonDeserializer<JSONPrope final JSONPropertyImpl property = new JSONPropertyImpl(); - if (tree.hasNonNull(Constants.JSON_METADATA)) { - property.setMetadata(URI.create(tree.get(Constants.JSON_METADATA).textValue())); + if (tree.hasNonNull(Constants.JSON_CONTEXT)) { + property.setContextURL(URI.create(tree.get(Constants.JSON_CONTEXT).textValue())); + tree.remove(Constants.JSON_CONTEXT); + } else if (tree.hasNonNull(Constants.JSON_METADATA)) { + property.setContextURL(URI.create(tree.get(Constants.JSON_METADATA).textValue())); tree.remove(Constants.JSON_METADATA); } - if (property.getMetadata() != null) { - final String metadataURI = property.getMetadata().toASCIIString(); - final int dashIdx = metadataURI.lastIndexOf('#'); + if (property.getContextURL() != null) { + final String contextURL = property.getContextURL().toASCIIString(); + final int dashIdx = contextURL.lastIndexOf('#'); if (dashIdx != -1) { - property.setType(metadataURI.substring(dashIdx + 1)); + property.setType(contextURL.substring(dashIdx + 1)); } } @@ -63,7 +66,7 @@ public class JSONPropertyDeserializer extends AbstractJsonDeserializer<JSONPrope } if (property.getValue() == null) { - value(property, tree.has(Constants.JSON_VALUE) ? tree.get(Constants.JSON_VALUE) : tree); + value(property, tree.has(Constants.VALUE) ? tree.get(Constants.VALUE) : tree); } return property; diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONPropertyImpl.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONPropertyImpl.java index 027eac33e..1ef029433 100644 --- a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONPropertyImpl.java +++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONPropertyImpl.java @@ -20,7 +20,6 @@ package org.apache.olingo.commons.core.data; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import java.net.URI; /** * A single property (primitive, complex or collection) represented via JSON. @@ -31,23 +30,4 @@ public class JSONPropertyImpl extends AbstractPropertyImpl { private static final long serialVersionUID = 553414431536637434L; - private URI metadata; - - /** - * Gets metadata URI. - * - * @return metadata URI. - */ - public URI getMetadata() { - return metadata; - } - - /** - * Sets metadata URI. - * - * @param metadata metadata URI. - */ - public void setMetadata(final URI metadata) { - this.metadata = metadata; - } } diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONPropertySerializer.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONPropertySerializer.java index 104083bc3..ce5f8916e 100644 --- a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONPropertySerializer.java +++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONPropertySerializer.java @@ -24,6 +24,7 @@ import com.fasterxml.jackson.databind.SerializerProvider; import java.io.IOException; import org.apache.olingo.commons.api.Constants; import org.apache.olingo.commons.api.data.Property; +import org.apache.olingo.commons.api.edm.constants.ODataServiceVersion; /** * Writes out JSON string from <tt>JSONPropertyImpl</tt>. @@ -38,16 +39,18 @@ public class JSONPropertySerializer extends AbstractJsonSerializer<JSONPropertyI jgen.writeStartObject(); - if (property.getMetadata() != null) { - jgen.writeStringField(Constants.JSON_METADATA, property.getMetadata().toASCIIString()); + if (property.getContextURL() != null) { + jgen.writeStringField( + version == ODataServiceVersion.V40 ? Constants.JSON_CONTEXT : Constants.JSON_METADATA, + property.getContextURL().toASCIIString()); } if (property.getValue().isNull()) { jgen.writeBooleanField(Constants.JSON_NULL, true); } else if (property.getValue().isSimple()) { - jgen.writeStringField(Constants.JSON_VALUE, property.getValue().asSimple().get()); + jgen.writeStringField(Constants.VALUE, property.getValue().asSimple().get()); } else if (property.getValue().isGeospatial() || property.getValue().isCollection()) { - property(jgen, property, Constants.JSON_VALUE); + property(jgen, property, Constants.VALUE); } else if (property.getValue().isComplex()) { for (Property cproperty : property.getValue().asComplex().get()) { property(jgen, cproperty, cproperty.getName()); diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/edm/EdmTypeInfo.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/edm/EdmTypeInfo.java index 09e70cb84..97b01b374 100644 --- a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/edm/EdmTypeInfo.java +++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/edm/EdmTypeInfo.java @@ -96,7 +96,6 @@ public class EdmTypeInfo { baseType = typeExpression.substring(collStartIdx + 11, collEndIdx); } - baseType = baseType.replaceAll("^#", ""); final String typeName;