diff --git a/lib/commons-core/src/test/java/org/apache/olingo/commons/core/DecoderTest.java b/lib/commons-core/src/test/java/org/apache/olingo/commons/core/DecoderTest.java index 019ef5a4f..09f232697 100644 --- a/lib/commons-core/src/test/java/org/apache/olingo/commons/core/DecoderTest.java +++ b/lib/commons-core/src/test/java/org/apache/olingo/commons/core/DecoderTest.java @@ -6,9 +6,9 @@ * 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 @@ -18,13 +18,13 @@ ******************************************************************************/ package org.apache.olingo.commons.core; -import org.junit.Test; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; +import org.junit.Test; + /** - * + * */ public class DecoderTest { diff --git a/lib/commons-core/src/test/java/org/apache/olingo/commons/core/EncoderTest.java b/lib/commons-core/src/test/java/org/apache/olingo/commons/core/EncoderTest.java index 74eb1af77..7db30cea4 100644 --- a/lib/commons-core/src/test/java/org/apache/olingo/commons/core/EncoderTest.java +++ b/lib/commons-core/src/test/java/org/apache/olingo/commons/core/EncoderTest.java @@ -6,9 +6,9 @@ * 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 @@ -18,16 +18,16 @@ ******************************************************************************/ package org.apache.olingo.commons.core; -import org.junit.Test; +import static org.junit.Assert.assertEquals; import java.net.URI; import java.net.URISyntaxException; -import static org.junit.Assert.assertEquals; +import org.junit.Test; /** * Tests for percent-encoding. - * + * */ public class EncoderTest { diff --git a/lib/pom.xml b/lib/pom.xml index 6843ff376..d1e88649b 100644 --- a/lib/pom.xml +++ b/lib/pom.xml @@ -42,6 +42,7 @@ client-core server-api server-core + server-core-ext server-tecsvc server-test diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/deserializer/ODataDeserializer.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/deserializer/ODataDeserializer.java index b361844a7..9aabf332e 100644 --- a/lib/server-api/src/main/java/org/apache/olingo/server/api/deserializer/ODataDeserializer.java +++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/deserializer/ODataDeserializer.java @@ -6,9 +6,9 @@ * 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 @@ -19,13 +19,16 @@ package org.apache.olingo.server.api.deserializer; import java.io.InputStream; +import java.net.URI; import java.util.List; import org.apache.olingo.commons.api.data.Entity; import org.apache.olingo.commons.api.data.EntitySet; import org.apache.olingo.commons.api.data.Parameter; +import org.apache.olingo.commons.api.data.Property; import org.apache.olingo.commons.api.edm.EdmAction; import org.apache.olingo.commons.api.edm.EdmEntityType; +import org.apache.olingo.commons.api.edm.EdmProperty; /** * Deserializer on OData server side. @@ -34,7 +37,7 @@ public interface ODataDeserializer { /** * Deserializes an entity stream into an {@link Entity} object. - * Validates: property types, no double properties, correct json types + * Validates: property types, no double properties, correct json types * @param stream * @param edmEntityType * @return deserialized {@link Entity} object @@ -50,7 +53,7 @@ public interface ODataDeserializer { * @throws DeserializerException */ EntitySet entityCollection(InputStream stream, EdmEntityType edmEntityType) throws DeserializerException; - + /** * Deserializes an action-parameters stream into an {@link Entity} object. * Validates: parameter types, no double parameters, correct json types. @@ -60,4 +63,22 @@ public interface ODataDeserializer { * @throws DeserializerException */ List actionParameters(InputStream stream, EdmAction edmAction) throws DeserializerException; + + /** + * Deserializes the Property or collections of properties (primitive & complex) + * @param stream + * @param edmProperty + * @return deserialized {@link Property} + * @throws DeserializerException + */ + Property property(InputStream stream, EdmProperty edmProperty) throws DeserializerException; + + /** + * Read entity references from the provided document + * @param stream + * @param keys + * @return + * @throws DeserializerException + */ + List entityReferences(InputStream stream) throws DeserializerException; } diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/EntityCollectionSerializerOptions.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/EntityCollectionSerializerOptions.java index 14c588d25..e5dd6b0bc 100644 --- a/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/EntityCollectionSerializerOptions.java +++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/EntityCollectionSerializerOptions.java @@ -6,9 +6,9 @@ * 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 @@ -30,6 +30,7 @@ public class EntityCollectionSerializerOptions { private CountOption count; private ExpandOption expand; private SelectOption select; + private boolean onlyReferences; /** Gets the {@link ContextURL}. */ public ContextURL getContextURL() { @@ -51,6 +52,11 @@ public class EntityCollectionSerializerOptions { return select; } + /** only writes the references of the entities*/ + public boolean onlyReferences() { + return onlyReferences; + } + /** Initializes the options builder. */ public static Builder with() { return new Builder(); @@ -59,7 +65,7 @@ public class EntityCollectionSerializerOptions { /** Builder of OData serializer options. */ public static final class Builder { - private EntityCollectionSerializerOptions options; + private final EntityCollectionSerializerOptions options; private Builder() { options = new EntityCollectionSerializerOptions(); @@ -89,6 +95,12 @@ public class EntityCollectionSerializerOptions { return this; } + /** Sets to serialize only references */ + public Builder setWriteOnlyReferences(final boolean ref) { + options.onlyReferences = ref; + return this; + } + /** Builds the OData serializer options. */ public EntityCollectionSerializerOptions build() { return options; diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/EntitySerializerOptions.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/EntitySerializerOptions.java index fcbd150fd..0abb31cf2 100644 --- a/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/EntitySerializerOptions.java +++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/EntitySerializerOptions.java @@ -6,9 +6,9 @@ * 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 @@ -27,6 +27,7 @@ public class EntitySerializerOptions { private ContextURL contextURL; private ExpandOption expand; private SelectOption select; + private boolean onlyReferences; /** Gets the {@link ContextURL}. */ public ContextURL getContextURL() { @@ -43,6 +44,11 @@ public class EntitySerializerOptions { return select; } + /** only writes the references of the entities*/ + public boolean onlyReferences() { + return onlyReferences; + } + private EntitySerializerOptions() {} /** Initializes the options builder. */ @@ -53,7 +59,7 @@ public class EntitySerializerOptions { /** Builder of OData serializer options. */ public static final class Builder { - private EntitySerializerOptions options; + private final EntitySerializerOptions options; private Builder() { options = new EntitySerializerOptions(); @@ -77,6 +83,12 @@ public class EntitySerializerOptions { return this; } + /** Sets to serialize only references */ + public Builder setWriteOnlyReferences(final boolean ref) { + options.onlyReferences = ref; + return this; + } + /** Builds the OData serializer options. */ public EntitySerializerOptions build() { return options; diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/ODataSerializer.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/ODataSerializer.java index 72f8ee862..55377cea6 100644 --- a/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/ODataSerializer.java +++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/ODataSerializer.java @@ -6,9 +6,9 @@ * 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 @@ -39,7 +39,7 @@ public interface ODataSerializer { /** * Writes the service document into an InputStream. * @param edm the Entity Data Model - * @param serviceRoot the service-root URI of this OData service + * @param serviceRoot the service-root URI of this OData service */ InputStream serviceDocument(Edm edm, String serviceRoot) throws SerializerException; @@ -58,21 +58,23 @@ public interface ODataSerializer { /** * Writes entity-collection data into an InputStream. + * @param metadata Metadata for the service * @param entityType the {@link EdmEntityType} * @param entitySet the data of the entity set * @param options options for the serializer */ - InputStream entityCollection(EdmEntityType entityType, EntitySet entitySet, - EntityCollectionSerializerOptions options) throws SerializerException; + InputStream entityCollection(ServiceMetadata metadata, EdmEntityType entityType, + EntitySet entitySet, EntityCollectionSerializerOptions options) throws SerializerException; /** * Writes entity data into an InputStream. + * @param metadata Metadata for the service * @param entityType the {@link EdmEntityType} * @param entity the data of the entity * @param options options for the serializer */ - InputStream entity(EdmEntityType entityType, Entity entity, EntitySerializerOptions options) - throws SerializerException; + InputStream entity(ServiceMetadata metadata, EdmEntityType entityType, Entity entity, + EntitySerializerOptions options) throws SerializerException; /** * Writes primitive-type instance data into an InputStream. @@ -85,12 +87,13 @@ public interface ODataSerializer { /** * Writes complex-type instance data into an InputStream. + * @param metadata Metadata for the service * @param type complex type * @param property property value * @param options options for the serializer */ - InputStream complex(EdmComplexType type, Property property, ComplexSerializerOptions options) - throws SerializerException; + InputStream complex(ServiceMetadata metadata, EdmComplexType type, Property property, + ComplexSerializerOptions options) throws SerializerException; /** * Writes data of a collection of primitive-type instances into an InputStream. @@ -103,10 +106,11 @@ public interface ODataSerializer { /** * Writes data of a collection of complex-type instances into an InputStream. + * @param metadata Metadata for the service * @param type complex type * @param property property value * @param options options for the serializer */ - InputStream complexCollection(EdmComplexType type, Property property, ComplexSerializerOptions options) - throws SerializerException; + InputStream complexCollection(ServiceMetadata metadata, EdmComplexType type, Property property, + ComplexSerializerOptions options) throws SerializerException; } diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/SerializerException.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/SerializerException.java index 1583241b6..a7d067fa6 100644 --- a/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/SerializerException.java +++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/SerializerException.java @@ -6,9 +6,9 @@ * 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 @@ -37,7 +37,9 @@ public class SerializerException extends ODataTranslatedException { /** parameter: property name */ INCONSISTENT_PROPERTY_TYPE, /** parameter: property name */ MISSING_PROPERTY, /** parameters: property name, property value */ WRONG_PROPERTY_VALUE, - /** parameters: primitive-type name, value */ WRONG_PRIMITIVE_VALUE; + /** parameters: primitive-type name, value */ WRONG_PRIMITIVE_VALUE, + UNKNOWN_TYPE, + WRONG_BASE_TYPE; @Override public String getKey() { diff --git a/lib/server-core-ext/pom.xml b/lib/server-core-ext/pom.xml new file mode 100644 index 000000000..a5730c01b --- /dev/null +++ b/lib/server-core-ext/pom.xml @@ -0,0 +1,117 @@ + + + + 4.0.0 + + odata-server-core-ext + jar + ${project.artifactId} + + + org.apache.olingo + odata-lib + 4.0.0-beta-03-SNAPSHOT + .. + + + 9.2.7.v20150116 + + + + org.apache.olingo + odata-server-api + ${project.version} + + + org.apache.olingo + odata-server-core + ${project.version} + + + org.apache.olingo + odata-commons-core + ${project.version} + + + org.antlr + antlr4-runtime + + + javax.servlet + javax.servlet-api + provided + + + javax.xml.stream + stax-api + + + junit + junit + + + org.mockito + mockito-all + + + org.slf4j + slf4j-simple + + + commons-io + commons-io + test + + + org.eclipse.jetty + jetty-server + test + ${jetty-version} + + + org.eclipse.jetty + jetty-servlet + ${jetty-version} + test + + + javax.servlet + javax.servlet-api + + + + + org.eclipse.jetty + jetty-http + ${jetty-version} + test + + + org.eclipse.jetty + jetty-client + test + ${jetty-version} + + + + diff --git a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/ErrorHandler.java b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/ErrorHandler.java new file mode 100644 index 000000000..199c62d2a --- /dev/null +++ b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/ErrorHandler.java @@ -0,0 +1,125 @@ +/* + * 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.server.core; + +import java.io.ByteArrayInputStream; + +import org.apache.olingo.commons.api.format.ContentType; +import org.apache.olingo.commons.api.format.ODataFormat; +import org.apache.olingo.commons.api.http.HttpHeader; +import org.apache.olingo.commons.api.http.HttpStatusCode; +import org.apache.olingo.server.api.OData; +import org.apache.olingo.server.api.ODataRequest; +import org.apache.olingo.server.api.ODataResponse; +import org.apache.olingo.server.api.ODataServerError; +import org.apache.olingo.server.api.ServiceMetadata; +import org.apache.olingo.server.api.batch.exception.BatchDeserializerException; +import org.apache.olingo.server.api.deserializer.DeserializerException; +import org.apache.olingo.server.api.serializer.CustomContentTypeSupport; +import org.apache.olingo.server.api.serializer.ODataSerializer; +import org.apache.olingo.server.api.serializer.RepresentationType; +import org.apache.olingo.server.api.serializer.SerializerException; +import org.apache.olingo.server.api.uri.UriInfo; +import org.apache.olingo.server.core.uri.parser.Parser; +import org.apache.olingo.server.core.uri.parser.UriParserException; +import org.apache.olingo.server.core.uri.parser.UriParserSemanticException; +import org.apache.olingo.server.core.uri.parser.UriParserSyntaxException; +import org.apache.olingo.server.core.uri.validator.UriValidationException; + +public class ErrorHandler { + private final OData odata; + private final ServiceMetadata metadata; + private final CustomContentTypeSupport customContent; + + public ErrorHandler(OData odata, ServiceMetadata metadata, CustomContentTypeSupport customContent) { + this.odata = odata; + this.metadata = metadata; + this.customContent = customContent; + } + + public void handleException(Exception e, ODataRequest request, ODataResponse response) { + if (e instanceof UriValidationException) { + ODataServerError serverError = ODataExceptionHelper.createServerErrorObject((UriValidationException)e, null); + handleServerError(request, response, serverError); + } else if(e instanceof UriParserSemanticException) { + ODataServerError serverError = ODataExceptionHelper.createServerErrorObject((UriParserSemanticException)e, null); + handleServerError(request, response, serverError); + } else if(e instanceof UriParserSyntaxException) { + ODataServerError serverError = ODataExceptionHelper.createServerErrorObject((UriParserSyntaxException)e, null); + handleServerError(request, response, serverError); + } else if(e instanceof UriParserException) { + ODataServerError serverError = ODataExceptionHelper.createServerErrorObject((UriParserException)e, null); + handleServerError(request, response, serverError); + } else if(e instanceof ContentNegotiatorException) { + ODataServerError serverError = ODataExceptionHelper.createServerErrorObject((ContentNegotiatorException)e, null); + handleServerError(request, response, serverError); + } else if(e instanceof SerializerException) { + ODataServerError serverError = ODataExceptionHelper.createServerErrorObject((SerializerException)e, null); + handleServerError(request, response, serverError); + } else if(e instanceof BatchDeserializerException) { + ODataServerError serverError = ODataExceptionHelper.createServerErrorObject((BatchDeserializerException)e, null); + handleServerError(request, response, serverError); + } else if(e instanceof DeserializerException) { + ODataServerError serverError = ODataExceptionHelper.createServerErrorObject((DeserializerException)e, null); + handleServerError(request, response, serverError); + } else if(e instanceof ODataHandlerException) { + ODataServerError serverError = ODataExceptionHelper.createServerErrorObject((ODataHandlerException)e, null); + handleServerError(request, response, serverError); + } else { + ODataServerError serverError = ODataExceptionHelper.createServerErrorObject(e); + handleServerError(request, response, serverError); + } + } + + void handleServerError(final ODataRequest request, final ODataResponse response, + final ODataServerError serverError) { + ContentType requestedContentType; + try { + UriInfo uriInfo = new Parser().parseUri(request.getRawODataPath(), request.getRawQueryPath(), + null, this.metadata.getEdm()); + requestedContentType = ContentNegotiator.doContentNegotiation(uriInfo.getFormatOption(), + request, this.customContent, RepresentationType.ERROR); + } catch (final ContentNegotiatorException e) { + requestedContentType = ODataFormat.JSON.getContentType(); + } catch (UriParserException e) { + requestedContentType = ODataFormat.JSON.getContentType(); + } + processError(response, serverError, requestedContentType); + } + + void processError(ODataResponse response, ODataServerError serverError, + ContentType requestedContentType) { + try { + ODataSerializer serializer = this.odata.createSerializer(ODataFormat + .fromContentType(requestedContentType)); + response.setContent(serializer.error(serverError)); + response.setStatusCode(serverError.getStatusCode()); + response.setHeader(HttpHeader.CONTENT_TYPE, requestedContentType.toContentTypeString()); + } catch (Exception e) { + // This should never happen but to be sure we have this catch here + // to prevent sending a stacktrace to a client. + String responseContent = "{\"error\":{\"code\":null,\"message\":\"An unexpected exception occurred during " + + "error processing with message: " + e.getMessage() + "\"}}"; //$NON-NLS-1$ //$NON-NLS-2$ + response.setContent(new ByteArrayInputStream(responseContent.getBytes())); + response.setStatusCode(HttpStatusCode.INTERNAL_SERVER_ERROR.getStatusCode()); + response.setHeader(HttpHeader.CONTENT_TYPE, + ContentType.APPLICATION_JSON.toContentTypeString()); + } + } +} diff --git a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/MetadataParser.java b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/MetadataParser.java new file mode 100644 index 000000000..e34a28a6f --- /dev/null +++ b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/MetadataParser.java @@ -0,0 +1,679 @@ +/* + * 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.server.core; + +import java.io.Reader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.xml.namespace.QName; +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.events.Attribute; +import javax.xml.stream.events.EndElement; +import javax.xml.stream.events.StartElement; +import javax.xml.stream.events.XMLEvent; + +import org.apache.olingo.commons.api.edm.FullQualifiedName; +import org.apache.olingo.commons.api.edm.provider.Action; +import org.apache.olingo.commons.api.edm.provider.ActionImport; +import org.apache.olingo.commons.api.edm.provider.ComplexType; +import org.apache.olingo.commons.api.edm.provider.EdmProvider; +import org.apache.olingo.commons.api.edm.provider.EntityContainer; +import org.apache.olingo.commons.api.edm.provider.EntitySet; +import org.apache.olingo.commons.api.edm.provider.EntityType; +import org.apache.olingo.commons.api.edm.provider.EnumMember; +import org.apache.olingo.commons.api.edm.provider.EnumType; +import org.apache.olingo.commons.api.edm.provider.Function; +import org.apache.olingo.commons.api.edm.provider.FunctionImport; +import org.apache.olingo.commons.api.edm.provider.NavigationProperty; +import org.apache.olingo.commons.api.edm.provider.NavigationPropertyBinding; +import org.apache.olingo.commons.api.edm.provider.OnDelete; +import org.apache.olingo.commons.api.edm.provider.OnDeleteAction; +import org.apache.olingo.commons.api.edm.provider.Operation; +import org.apache.olingo.commons.api.edm.provider.Parameter; +import org.apache.olingo.commons.api.edm.provider.Property; +import org.apache.olingo.commons.api.edm.provider.PropertyRef; +import org.apache.olingo.commons.api.edm.provider.ReferentialConstraint; +import org.apache.olingo.commons.api.edm.provider.ReturnType; +import org.apache.olingo.commons.api.edm.provider.Schema; +import org.apache.olingo.commons.api.edm.provider.Singleton; +import org.apache.olingo.commons.api.edm.provider.Term; +import org.apache.olingo.commons.api.edm.provider.TypeDefinition; + +/** + * This class can convert a CSDL document into EDMProvider object + */ +public class MetadataParser { + + public EdmProvider buildEdmProvider(Reader csdl) throws XMLStreamException { + XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance(); + XMLEventReader reader = xmlInputFactory.createXMLEventReader(csdl); + + SchemaBasedEdmProvider provider = new SchemaBasedEdmProvider(); + new ElementReader() { + @Override + void build(XMLEventReader reader, StartElement element, SchemaBasedEdmProvider provider, + String name) throws XMLStreamException { + String version = attr(element, "Version"); + if (version.equals("4.0")) { + readDataServicesAndReference(reader, element, provider); + } + } + }.read(reader, null, provider, "Edmx"); + + return provider; + } + + private void readDataServicesAndReference(XMLEventReader reader, StartElement element, + SchemaBasedEdmProvider provider) throws XMLStreamException { + new ElementReader() { + @Override + void build(XMLEventReader reader, StartElement element, SchemaBasedEdmProvider provider, + String name) throws XMLStreamException { + if (name.equals("DataServices")) { + readSchema(reader, element, provider); + } else if (name.equals("Reference")) { + readReference(reader, element, provider, "Reference"); + } + } + }.read(reader, element, provider, "DataServices", "Reference"); + } + + private void readReference(XMLEventReader reader, StartElement element, + SchemaBasedEdmProvider provider, String name) throws XMLStreamException { + new ElementReader() { + @Override + void build(XMLEventReader reader, StartElement element, SchemaBasedEdmProvider t, String name) + throws XMLStreamException { + // TODO: + } + }.read(reader, element, provider, name); + } + + private void readSchema(XMLEventReader reader, StartElement element, + SchemaBasedEdmProvider provider) throws XMLStreamException { + + Schema schema = new Schema(); + schema.setComplexTypes(new ArrayList()); + schema.setActions(new ArrayList()); + schema.setEntityTypes(new ArrayList()); + schema.setEnumTypes(new ArrayList()); + schema.setFunctions(new ArrayList()); + schema.setTerms(new ArrayList()); + schema.setTypeDefinitions(new ArrayList()); + + new ElementReader() { + @Override + void build(XMLEventReader reader, StartElement element, Schema schema, String name) + throws XMLStreamException { + schema.setNamespace(attr(element, "Namespace")); + schema.setAlias(attr(element, "Alias")); + readSchemaContents(reader, schema); + } + }.read(reader, element, schema, "Schema"); + provider.addSchema(schema); + } + + private void readSchemaContents(XMLEventReader reader, Schema schema) throws XMLStreamException { + new ElementReader() { + @Override + void build(XMLEventReader reader, StartElement element, Schema schema, String name) + throws XMLStreamException { + if (name.equals("Action")) { + readAction(reader, element, schema); + } else if (name.equals("Annotations")) { + // TODO: + } else if (name.equals("Annotation")) { + // TODO: + } else if (name.equals("ComplexType")) { + readComplexType(reader, element, schema); + } else if (name.equals("EntityContainer")) { + readEntityContainer(reader, element, schema); + } else if (name.equals("EntityType")) { + readEntityType(reader, element, schema); + } else if (name.equals("EnumType")) { + readEnumType(reader, element, schema); + } else if (name.equals("Function")) { + readFunction(reader, element, schema); + } else if (name.equals("Term")) { + schema.getTerms().add(readTerm(element)); + } else if (name.equals("TypeDefinition")) { + schema.getTypeDefinitions().add(readTypeDefinition(element)); + } + } + }.read(reader, null, schema, "Action", "Annotations", "Annotation", "ComplexType", + "EntityContainer", "EntityType", "EnumType", "Function", "Term", "TypeDefinition"); + } + + private void readAction(XMLEventReader reader, StartElement element, Schema schema) + throws XMLStreamException { + + Action action = new Action(); + action.setParameters(new ArrayList()); + action.setName(attr(element, "Name")); + action.setBound(Boolean.parseBoolean(attr(element, "IsBound"))); + String entitySetPath = attr(element, "EntitySetPath"); + if (entitySetPath != null) { + // TODO: need to parse into binding and path. + action.setEntitySetPath(entitySetPath); + } + readOperationParameters(reader, action); + schema.getActions().add(action); + } + + private FullQualifiedName readType(StartElement element) { + String type = attr(element, "Type"); + if (type.startsWith("Collection(") && type.endsWith(")")) { + return new FullQualifiedName(type.substring(11, type.length() - 1)); + } + return new FullQualifiedName(type); + } + + private boolean isCollectionType(StartElement element) { + String type = attr(element, "Type"); + if (type.startsWith("Collection(") && type.endsWith(")")) { + return true; + } + return false; + } + + private void readReturnType(StartElement element, Operation operation) { + ReturnType returnType = new ReturnType(); + returnType.setType(readType(element)); + returnType.setCollection(isCollectionType(element)); + returnType.setNullable(Boolean.parseBoolean(attr(element, "Nullable"))); + + String maxLength = attr(element, "MaxLength"); + if (maxLength != null) { + returnType.setMaxLength(Integer.parseInt(maxLength)); + } + String precision = attr(element, "Precision"); + if (precision != null) { + returnType.setPrecision(Integer.parseInt(precision)); + } + String scale = attr(element, "Scale"); + if (scale != null) { + returnType.setScale(Integer.parseInt(scale)); + } + String srid = attr(element, "SRID"); + if (srid != null) { + // TODO: no olingo support yet. + } + operation.setReturnType(returnType); + } + + private void readParameter(StartElement element, Operation operation) { + Parameter parameter = new Parameter(); + parameter.setName(attr(element, "Name")); + parameter.setType(readType(element)); + parameter.setCollection(isCollectionType(element)); + parameter.setNullable(Boolean.parseBoolean(attr(element, "Nullable"))); + + String maxLength = attr(element, "MaxLength"); + if (maxLength != null) { + parameter.setMaxLength(Integer.parseInt(maxLength)); + } + String precision = attr(element, "Precision"); + if (precision != null) { + parameter.setPrecision(Integer.parseInt(precision)); + } + String scale = attr(element, "Scale"); + if (scale != null) { + parameter.setScale(Integer.parseInt(scale)); + } + String srid = attr(element, "SRID"); + if (srid != null) { + // TODO: no olingo support yet. + } + operation.getParameters().add(parameter); + } + + private TypeDefinition readTypeDefinition(StartElement element) { + TypeDefinition td = new TypeDefinition(); + td.setName(attr(element, "Name")); + td.setUnderlyingType(new FullQualifiedName(attr(element, "UnderlyingType"))); + td.setUnicode(Boolean.parseBoolean(attr(element, "Unicode"))); + + String maxLength = attr(element, "MaxLength"); + if (maxLength != null) { + td.setMaxLength(Integer.parseInt(maxLength)); + } + String precision = attr(element, "Precision"); + if (precision != null) { + td.setPrecision(Integer.parseInt(precision)); + } + String scale = attr(element, "Scale"); + if (scale != null) { + td.setScale(Integer.parseInt(scale)); + } + String srid = attr(element, "SRID"); + if (srid != null) { + // TODO: no olingo support yet. + } + return td; + } + + private Term readTerm(StartElement element) { + Term term = new Term(); + term.setName(attr(element, "Name")); + term.setType(attr(element, "Type")); + if (attr(element, "BaseTerm") != null) { + term.setBaseTerm(attr(element, "BaseTerm")); + } + if (attr(element, "DefaultValue") != null) { + term.setDefaultValue(attr(element, "DefaultValue")); + } + if (attr(element, "AppliesTo") != null) { + term.setAppliesTo(Arrays.asList(attr(element, "AppliesTo"))); + } + term.setNullable(Boolean.parseBoolean(attr(element, "Nullable"))); + String maxLength = attr(element, "MaxLength"); + if (maxLength != null) { + term.setMaxLength(Integer.parseInt(maxLength)); + } + String precision = attr(element, "Precision"); + if (precision != null) { + term.setPrecision(Integer.parseInt(precision)); + } + String scale = attr(element, "Scale"); + if (scale != null) { + term.setScale(Integer.parseInt(scale)); + } + String srid = attr(element, "SRID"); + if (srid != null) { + // TODO: no olingo support yet. + } + return term; + } + + private void readFunction(XMLEventReader reader, StartElement element, Schema schema) + throws XMLStreamException { + Function function = new Function(); + function.setParameters(new ArrayList()); + function.setName(attr(element, "Name")); + function.setBound(Boolean.parseBoolean(attr(element, "IsBound"))); + function.setComposable(Boolean.parseBoolean(attr(element, "IsComposable"))); + String entitySetPath = attr(element, "EntitySetPath"); + if (entitySetPath != null) { + // TODO: need to parse into binding and path. + function.setEntitySetPath(entitySetPath); + } + readOperationParameters(reader, function); + schema.getFunctions().add(function); + } + + private void readOperationParameters(XMLEventReader reader, final Operation operation) + throws XMLStreamException { + new ElementReader() { + @Override + void build(XMLEventReader reader, StartElement element, Operation operation, String name) + throws XMLStreamException { + if (name.equals("Parameter")) { + readParameter(element, operation); + } else if (name.equals("ReturnType")) { + readReturnType(element, operation); + } + } + }.read(reader, null, operation, "Parameter", "ReturnType"); + } + + private void readEnumType(XMLEventReader reader, StartElement element, Schema schema) + throws XMLStreamException { + EnumType type = new EnumType(); + type.setMembers(new ArrayList()); + type.setName(attr(element, "Name")); + if (attr(element, "UnderlyingType") != null) { + type.setUnderlyingType(new FullQualifiedName(attr(element, "UnderlyingType"))); + } + type.setFlags(Boolean.parseBoolean(attr(element, "IsFlags"))); + + readEnumMembers(reader, element, type); + schema.getEnumTypes().add(type); + } + + private void readEnumMembers(XMLEventReader reader, StartElement element, EnumType type) + throws XMLStreamException { + new ElementReader() { + @Override + void build(XMLEventReader reader, StartElement element, EnumType type, String name) + throws XMLStreamException { + EnumMember member = new EnumMember(); + member.setName(attr(element, "Name")); + member.setValue(attr(element, "Value")); + type.getMembers().add(member); + } + }.read(reader, element, type, "Member"); + } + + private void readEntityType(XMLEventReader reader, StartElement element, Schema schema) + throws XMLStreamException { + EntityType entityType = new EntityType(); + entityType.setProperties(new ArrayList()); + entityType.setNavigationProperties(new ArrayList()); + entityType.setKey(new ArrayList()); + entityType.setName(attr(element, "Name")); + if (attr(element, "BaseType") != null) { + entityType.setBaseType(new FullQualifiedName(attr(element, "BaseType"))); + } + entityType.setAbstract(Boolean.parseBoolean(attr(element, "Abstract"))); + entityType.setOpenType(Boolean.parseBoolean(attr(element, "OpenType"))); + entityType.setHasStream(Boolean.parseBoolean(attr(element, "HasStream"))); + readEntityProperties(reader, entityType); + schema.getEntityTypes().add(entityType); + } + + private void readEntityProperties(XMLEventReader reader, EntityType entityType) + throws XMLStreamException { + new ElementReader() { + @Override + void build(XMLEventReader reader, StartElement element, EntityType entityType, String name) + throws XMLStreamException { + if (name.equals("Property")) { + entityType.getProperties().add(readProperty(element)); + } else if (name.equals("NavigationProperty")) { + entityType.getNavigationProperties().add(readNavigationProperty(reader, element)); + } else if (name.equals("Key")) { + readKey(reader, element, entityType); + } + } + }.read(reader, null, entityType, "Property", "NavigationProperty", "Key"); + } + + private void readKey(XMLEventReader reader, StartElement element, EntityType entityType) + throws XMLStreamException { + new ElementReader() { + @Override + void build(XMLEventReader reader, StartElement element, EntityType entityType, String name) + throws XMLStreamException { + PropertyRef ref = new PropertyRef(); + ref.setName(attr(element, "Name")); + ref.setAlias(attr(element, "Alias")); + entityType.getKey().add(ref); + } + }.read(reader, element, entityType, "PropertyRef"); + } + + private NavigationProperty readNavigationProperty(XMLEventReader reader, StartElement element) + throws XMLStreamException { + NavigationProperty property = new NavigationProperty(); + property.setReferentialConstraints(new ArrayList()); + + property.setName(attr(element, "Name")); + property.setType(readType(element)); + property.setCollection(isCollectionType(element)); + property.setNullable(Boolean.parseBoolean(attr(element, "Nullable"))); + property.setPartner(attr(element, "Partner")); + property.setContainsTarget(Boolean.parseBoolean(attr(element, "ContainsTarget"))); + + new ElementReader() { + @Override + void build(XMLEventReader reader, StartElement element, NavigationProperty property, + String name) throws XMLStreamException { + if (name.equals("ReferentialConstraint")) { + ReferentialConstraint constraint = new ReferentialConstraint(); + constraint.setProperty(attr(element, "Property")); + constraint.setReferencedProperty(attr(element, "ReferencedProperty")); + property.getReferentialConstraints().add(constraint); + } else if (name.equals("OnDelete")) { + property.setOnDelete(new OnDelete().setAction(OnDeleteAction.valueOf(attr(element, "Action")))); + } + } + }.read(reader, element, property, "ReferentialConstraint", "OnDelete"); + return property; + } + + private String attr(StartElement element, String name) { + Attribute attr = element.getAttributeByName(new QName(name)); + if (attr != null) { + return attr.getValue(); + } + return null; + } + + private Property readProperty(StartElement element) { + Property property = new Property(); + property.setName(attr(element, "Name")); + property.setType(readType(element)); + property.setCollection(isCollectionType(element)); + property.setNullable(Boolean.parseBoolean(attr(element, "Nullable") == null ? "true" : attr( + element, "Nullable"))); + property.setUnicode(Boolean.parseBoolean(attr(element, "Unicode"))); + + String maxLength = attr(element, "MaxLength"); + if (maxLength != null) { + property.setMaxLength(Integer.parseInt(maxLength)); + } + String precision = attr(element, "Precision"); + if (precision != null) { + property.setPrecision(Integer.parseInt(precision)); + } + String scale = attr(element, "Scale"); + if (scale != null) { + property.setScale(Integer.parseInt(scale)); + } + String srid = attr(element, "SRID"); + if (srid != null) { + // TODO: no olingo support yet. + } + String defaultValue = attr(element, "DefaultValue"); + if (defaultValue != null) { + property.setDefaultValue(defaultValue); + } + return property; + } + + private void readEntityContainer(XMLEventReader reader, StartElement element, Schema schema) + throws XMLStreamException { + final EntityContainer container = new EntityContainer(); + container.setName(attr(element, "Name")); + if (attr(element, "Extends") != null) { + container.setExtendsContainer(attr(element, "Extends")); + } + container.setActionImports(new ArrayList()); + container.setFunctionImports(new ArrayList()); + container.setEntitySets(new ArrayList()); + container.setSingletons(new ArrayList()); + + new ElementReader() { + @Override + void build(XMLEventReader reader, StartElement element, Schema schema, String name) + throws XMLStreamException { + if (name.equals("EntitySet")) { + readEntitySet(reader, element, container); + } else if (name.equals("Singleton")) { + readSingleton(reader, element, container); + } else if (name.equals("ActionImport")) { + readActionImport(element, container); + } else if (name.equals("FunctionImport")) { + readFunctionImport(element, container); + } + } + + private void readFunctionImport(StartElement element, EntityContainer container) { + FunctionImport functionImport = new FunctionImport(); + functionImport.setName(attr(element, "Name")); + functionImport.setFunction(new FullQualifiedName(attr(element, "Function"))); + functionImport.setIncludeInServiceDocument(Boolean.parseBoolean(attr(element, + "IncludeInServiceDocument"))); + + String entitySet = attr(element, "EntitySet"); + if (entitySet != null) { + functionImport.setEntitySet(entitySet); + } + container.getFunctionImports().add(functionImport); + } + + private void readActionImport(StartElement element, EntityContainer container) { + ActionImport actionImport = new ActionImport(); + actionImport.setName(attr(element, "Name")); + actionImport.setAction(new FullQualifiedName(attr(element, "Action"))); + + String entitySet = attr(element, "EntitySet"); + if (entitySet != null) { + actionImport.setEntitySet(entitySet); + } + container.getActionImports().add(actionImport); + } + + private void readSingleton(XMLEventReader reader, StartElement element, + EntityContainer container) throws XMLStreamException { + Singleton singleton = new Singleton(); + singleton.setNavigationPropertyBindings(new ArrayList()); + singleton.setName(attr(element, "Name")); + singleton.setType(new FullQualifiedName(attr(element, "Type"))); + singleton.setNavigationPropertyBindings(new ArrayList()); + readNavigationPropertyBindings(reader, element, singleton.getNavigationPropertyBindings()); + container.getSingletons().add(singleton); + } + + private void readEntitySet(XMLEventReader reader, StartElement element, + EntityContainer container) throws XMLStreamException { + EntitySet entitySet = new EntitySet(); + entitySet.setName(attr(element, "Name")); + entitySet.setType(new FullQualifiedName(attr(element, "EntityType"))); + entitySet.setIncludeInServiceDocument(Boolean.parseBoolean(attr(element, + "IncludeInServiceDocument"))); + entitySet.setNavigationPropertyBindings(new ArrayList()); + readNavigationPropertyBindings(reader, element, entitySet.getNavigationPropertyBindings()); + container.getEntitySets().add(entitySet); + } + + private void readNavigationPropertyBindings(XMLEventReader reader, StartElement element, + List bindings) throws XMLStreamException { + new ElementReader>() { + @Override + void build(XMLEventReader reader, StartElement element, + List bindings, String name) throws XMLStreamException { + NavigationPropertyBinding binding = new NavigationPropertyBinding(); + binding.setPath(attr(element, "Path")); + binding.setTarget(attr(element, "Target")); + bindings.add(binding); + } + + }.read(reader, element, bindings, "NavigationPropertyBinding"); + ; + } + }.read(reader, element, schema, "EntitySet", "Singleton", "ActionImport", "FunctionImport"); + schema.setEntityContainer(container); + } + + private void readComplexType(XMLEventReader reader, StartElement element, Schema schema) + throws XMLStreamException { + ComplexType complexType = new ComplexType(); + complexType.setProperties(new ArrayList()); + complexType.setNavigationProperties(new ArrayList()); + complexType.setName(attr(element, "Name")); + if (attr(element, "BaseType") != null) { + complexType.setBaseType(new FullQualifiedName(attr(element, "BaseType"))); + } + complexType.setAbstract(Boolean.parseBoolean(attr(element, "Abstract"))); + complexType.setOpenType(Boolean.parseBoolean(attr(element, "OpenType"))); + readProperties(reader, complexType); + + schema.getComplexTypes().add(complexType); + } + + private void readProperties(XMLEventReader reader, ComplexType complexType) + throws XMLStreamException { + new ElementReader() { + @Override + void build(XMLEventReader reader, StartElement element, ComplexType complexType, String name) + throws XMLStreamException { + if (name.equals("Property")) { + complexType.getProperties().add(readProperty(element)); + } else if (name.equals("NavigationProperty")) { + complexType.getNavigationProperties().add(readNavigationProperty(reader, element)); + } + } + }.read(reader, null, complexType, "Property", "NavigationProperty"); + } + + abstract class ElementReader { + void read(XMLEventReader reader, StartElement element, T t, String... names) + throws XMLStreamException { + while (reader.hasNext()) { + XMLEvent event = reader.peek(); + + event = skipAnnotations(reader, event); + + if (!event.isStartElement() && !event.isEndElement()) { + reader.nextEvent(); + continue; + } + + boolean hit = false; + + for (int i = 0; i < names.length; i++) { + if (event.isStartElement()) { + element = event.asStartElement(); + if (element.getName().getLocalPart().equals(names[i])) { + reader.nextEvent(); // advance cursor + // System.out.println("reading = "+names[i]); + build(reader, element, t, names[i]); + hit = true; + } + } + if (event.isEndElement()) { + EndElement e = event.asEndElement(); + if (e.getName().getLocalPart().equals(names[i])) { + reader.nextEvent(); // advance cursor + // System.out.println("done reading = "+names[i]); + hit = true; + } + } + } + if (!hit) { + break; + } + } + } + + private XMLEvent skipAnnotations(XMLEventReader reader, XMLEvent event) + throws XMLStreamException { + boolean skip = false; + + while (reader.hasNext()) { + if (event.isStartElement()) { + StartElement element = event.asStartElement(); + if (element.getName().getLocalPart().equals("Annotation")) { + skip = true; + } + } + if (event.isEndElement()) { + EndElement element = event.asEndElement(); + if (element.getName().getLocalPart().equals("Annotation")) { + return reader.peek(); + } + } + if (skip) { + event = reader.nextEvent(); + } else { + return event; + } + } + return event; + } + + abstract void build(XMLEventReader reader, StartElement element, T t, String name) + throws XMLStreamException; + } +} diff --git a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/OData4HttpHandler.java b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/OData4HttpHandler.java new file mode 100644 index 000000000..ddb8e6b78 --- /dev/null +++ b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/OData4HttpHandler.java @@ -0,0 +1,123 @@ +/* + * 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.server.core; + +import java.io.IOException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.olingo.commons.api.edm.constants.ODataServiceVersion; +import org.apache.olingo.commons.api.http.HttpHeader; +import org.apache.olingo.server.api.OData; +import org.apache.olingo.server.api.ODataRequest; +import org.apache.olingo.server.api.ODataResponse; +import org.apache.olingo.server.api.ODataTranslatedException; +import org.apache.olingo.server.api.ServiceMetadata; +import org.apache.olingo.server.api.processor.Processor; +import org.apache.olingo.server.api.serializer.CustomContentTypeSupport; +import org.apache.olingo.server.api.serializer.SerializerException; +import org.apache.olingo.server.core.legacy.ProcessorServiceHandler; + +public class OData4HttpHandler extends ODataHttpHandlerImpl { + private ServiceHandler handler; + private final ServiceMetadata serviceMetadata; + private final OData odata; + private CustomContentTypeSupport customContentTypeSupport; + + + public OData4HttpHandler(OData odata, ServiceMetadata serviceMetadata) { + super(odata, serviceMetadata); + this.odata = odata; + this.serviceMetadata = serviceMetadata; + // this is support old interfaces + this.handler = new ProcessorServiceHandler(); + this.handler.init(odata, serviceMetadata); + } + + @Override + public void process(final HttpServletRequest httpRequest, final HttpServletResponse httpResponse) { + ODataRequest request = null; + ODataResponse response = new ODataResponse(); + + try { + request = createODataRequest(httpRequest, 0); + validateODataVersion(request, response); + + ServiceDispatcher dispatcher = new ServiceDispatcher(this.odata, this.serviceMetadata, + handler, this.customContentTypeSupport); + dispatcher.execute(request, response); + + } catch (Exception e) { + ErrorHandler handler = new ErrorHandler(this.odata, this.serviceMetadata, + this.customContentTypeSupport); + handler.handleException(e, request, response); + } + convertToHttp(httpResponse, response); + } + + + ODataRequest createODataRequest(final HttpServletRequest httpRequest, final int split) + throws ODataTranslatedException { + try { + ODataRequest odRequest = new ODataRequest(); + + odRequest.setBody(httpRequest.getInputStream()); + extractHeaders(odRequest, httpRequest); + extractMethod(odRequest, httpRequest); + extractUri(odRequest, httpRequest, split); + + return odRequest; + } catch (final IOException e) { + throw new SerializerException( + "An I/O exception occurred.", e, SerializerException.MessageKeys.IO_EXCEPTION); //$NON-NLS-1$ + } + } + + void validateODataVersion(final ODataRequest request, final ODataResponse response) + throws ODataHandlerException { + final String maxVersion = request.getHeader(HttpHeader.ODATA_MAX_VERSION); + response.setHeader(HttpHeader.ODATA_VERSION, ODataServiceVersion.V40.toString()); + + if (maxVersion != null) { + if (ODataServiceVersion.isBiggerThan(ODataServiceVersion.V40.toString(), maxVersion)) { + throw new ODataHandlerException("ODataVersion not supported: " + maxVersion, //$NON-NLS-1$ + ODataHandlerException.MessageKeys.ODATA_VERSION_NOT_SUPPORTED, maxVersion); + } + } + } + + @Override + public void register(final Processor processor) { + + if (processor instanceof ServiceHandler) { + this.handler = (ServiceHandler) processor; + this.handler.init(this.odata, this.serviceMetadata); + } + + if (this.handler instanceof ProcessorServiceHandler) { + ((ProcessorServiceHandler)this.handler).register(processor); + } + } + + @Override + public void register(final CustomContentTypeSupport customContentTypeSupport) { + this.customContentTypeSupport = customContentTypeSupport; + } +} diff --git a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/OData4Impl.java b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/OData4Impl.java new file mode 100644 index 000000000..bde9c966c --- /dev/null +++ b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/OData4Impl.java @@ -0,0 +1,45 @@ +/* + * 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.server.core; + +import org.apache.olingo.commons.api.ODataRuntimeException; +import org.apache.olingo.server.api.OData; +import org.apache.olingo.server.api.ODataHttpHandler; +import org.apache.olingo.server.api.ServiceMetadata; + +public class OData4Impl extends ODataImpl { + + public static OData newInstance() { + try { + final Class clazz = Class.forName(OData4Impl.class.getName()); + final Object object = clazz.newInstance(); + return (OData) object; + } catch (final Exception e) { + throw new ODataRuntimeException(e); + } + } + + private OData4Impl() { + } + + @Override + public ODataHttpHandler createHandler(final ServiceMetadata edm) { + return new OData4HttpHandler(this, edm); + } +} diff --git a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/RequestURLHierarchyVisitor.java b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/RequestURLHierarchyVisitor.java new file mode 100644 index 000000000..ee0063819 --- /dev/null +++ b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/RequestURLHierarchyVisitor.java @@ -0,0 +1,333 @@ +/* + * 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.server.core; + +import java.util.List; + +import org.apache.olingo.commons.api.edm.EdmEntityType; +import org.apache.olingo.server.api.uri.UriInfo; +import org.apache.olingo.server.api.uri.UriInfoAll; +import org.apache.olingo.server.api.uri.UriInfoBatch; +import org.apache.olingo.server.api.uri.UriInfoCrossjoin; +import org.apache.olingo.server.api.uri.UriInfoEntityId; +import org.apache.olingo.server.api.uri.UriInfoKind; +import org.apache.olingo.server.api.uri.UriInfoMetadata; +import org.apache.olingo.server.api.uri.UriInfoResource; +import org.apache.olingo.server.api.uri.UriInfoService; +import org.apache.olingo.server.api.uri.UriResource; +import org.apache.olingo.server.api.uri.UriResourceAction; +import org.apache.olingo.server.api.uri.UriResourceComplexProperty; +import org.apache.olingo.server.api.uri.UriResourceCount; +import org.apache.olingo.server.api.uri.UriResourceEntitySet; +import org.apache.olingo.server.api.uri.UriResourceFunction; +import org.apache.olingo.server.api.uri.UriResourceIt; +import org.apache.olingo.server.api.uri.UriResourceLambdaAll; +import org.apache.olingo.server.api.uri.UriResourceLambdaAny; +import org.apache.olingo.server.api.uri.UriResourceLambdaVariable; +import org.apache.olingo.server.api.uri.UriResourceNavigation; +import org.apache.olingo.server.api.uri.UriResourcePrimitiveProperty; +import org.apache.olingo.server.api.uri.UriResourceRef; +import org.apache.olingo.server.api.uri.UriResourceRoot; +import org.apache.olingo.server.api.uri.UriResourceSingleton; +import org.apache.olingo.server.api.uri.UriResourceValue; +import org.apache.olingo.server.api.uri.queryoption.CountOption; +import org.apache.olingo.server.api.uri.queryoption.ExpandOption; +import org.apache.olingo.server.api.uri.queryoption.FilterOption; +import org.apache.olingo.server.api.uri.queryoption.FormatOption; +import org.apache.olingo.server.api.uri.queryoption.IdOption; +import org.apache.olingo.server.api.uri.queryoption.OrderByOption; +import org.apache.olingo.server.api.uri.queryoption.SearchOption; +import org.apache.olingo.server.api.uri.queryoption.SelectOption; +import org.apache.olingo.server.api.uri.queryoption.SkipOption; +import org.apache.olingo.server.api.uri.queryoption.SkipTokenOption; +import org.apache.olingo.server.api.uri.queryoption.TopOption; + +public class RequestURLHierarchyVisitor implements RequestURLVisitor { + + private UriInfo uriInfo; + + public UriInfo getUriInfo() { + return this.uriInfo; + } + + @Override + public void visit(UriInfo info) { + this.uriInfo = info; + + UriInfoKind kind = info.getKind(); + switch (kind) { + case all: + visit(info.asUriInfoAll()); + break; + case batch: + visit(info.asUriInfoBatch()); + break; + case crossjoin: + visit(info.asUriInfoCrossjoin()); + break; + case entityId: + visit(info.asUriInfoEntityId()); + break; + case metadata: + visit(info.asUriInfoMetadata()); + break; + case resource: + visit(info.asUriInfoResource()); + break; + case service: + visit(info.asUriInfoService()); + break; + } + } + + @Override + public void visit(UriInfoService info) { + } + + @Override + public void visit(UriInfoAll info) { + } + + @Override + public void visit(UriInfoBatch info) { + } + + @Override + public void visit(UriInfoCrossjoin info) { + } + + @Override + public void visit(UriInfoEntityId info) { + visit(info.getSelectOption()); + + if (info.getExpandOption() != null) { + visit(info.getExpandOption()); + } + if (info.getFormatOption() != null) { + visit(info.getFormatOption()); + } + if (info.getIdOption() != null) { + visit(info.getIdOption(), info.getEntityTypeCast()); + } + } + + @Override + public void visit(UriInfoMetadata info) { + } + + @Override + public void visit(UriInfoResource info) { + List parts = info.getUriResourceParts(); + for (UriResource resource : parts) { + switch (resource.getKind()) { + case action: + visit((UriResourceAction) resource); + break; + case complexProperty: + visit((UriResourceComplexProperty) resource); + break; + case count: + visit((UriResourceCount) resource); + break; + case entitySet: + visit((UriResourceEntitySet) resource); + break; + case function: + visit((UriResourceFunction) resource); + break; + case it: + visit((UriResourceIt) resource); + break; + case lambdaAll: + visit((UriResourceLambdaAll) resource); + break; + case lambdaAny: + visit((UriResourceLambdaAny) resource); + break; + case lambdaVariable: + visit((UriResourceLambdaVariable) resource); + break; + case navigationProperty: + visit((UriResourceNavigation) resource); + break; + case ref: + visit((UriResourceRef) resource); + break; + case root: + visit((UriResourceRoot) resource); + break; + case primitiveProperty: + visit((UriResourcePrimitiveProperty) resource); + break; + case singleton: + visit((UriResourceSingleton) resource); + break; + case value: + visit((UriResourceValue) resource); + break; + } + } + + // http://docs.oasis-open.org/odata/odata/v4.0/os/part1-protocol/odata-v4.0-os-part1-protocol.html#_Toc372793682 + if (info.getSearchOption() != null) { + visit(info.getSearchOption()); + } + + if (info.getFilterOption() != null) { + visit(info.getFilterOption()); + } + + if (info.getCountOption() != null) { + visit(info.getCountOption()); + } + + visit(info.getOrderByOption()); + + if (info.getSkipOption() != null) { + visit(info.getSkipOption()); + } + + if (info.getTopOption() != null) { + visit(info.getTopOption()); + } + + if (info.getExpandOption() != null) { + visit(info.getExpandOption()); + } + + visit(info.getSelectOption()); + + if (info.getFormatOption() != null) { + visit(info.getFormatOption()); + } + + if (info.getIdOption() != null) { + visit(info.getIdOption(), null); + } + + if (info.getSkipTokenOption() != null) { + visit(info.getSkipTokenOption()); + } + + } + + @Override + public void visit(ExpandOption option) { + } + + @Override + public void visit(FilterOption info) { + } + + @Override + public void visit(FormatOption info) { + } + + @Override + public void visit(IdOption info, EdmEntityType type) { + } + + @Override + public void visit(CountOption info) { + } + + @Override + public void visit(OrderByOption option) { + } + + @Override + public void visit(SearchOption option) { + } + + @Override + public void visit(SelectOption option) { + } + + @Override + public void visit(SkipOption option) { + } + + @Override + public void visit(SkipTokenOption option) { + } + + @Override + public void visit(TopOption option) { + } + + @Override + public void visit(UriResourceCount option) { + } + + @Override + public void visit(UriResourceRef info) { + } + + @Override + public void visit(UriResourceRoot info) { + } + + @Override + public void visit(UriResourceValue info) { + } + + @Override + public void visit(UriResourceAction info) { + } + + @Override + public void visit(UriResourceEntitySet info) { + } + + @Override + public void visit(UriResourceFunction info) { + } + + @Override + public void visit(UriResourceIt info) { + } + + @Override + public void visit(UriResourceLambdaAll info) { + } + + @Override + public void visit(UriResourceLambdaAny info) { + } + + @Override + public void visit(UriResourceLambdaVariable info) { + } + + @Override + public void visit(UriResourceNavigation info) { + } + + @Override + public void visit(UriResourceSingleton info) { + } + + @Override + public void visit(UriResourceComplexProperty info) { + } + + @Override + public void visit(UriResourcePrimitiveProperty info) { + } +} diff --git a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/RequestURLVisitor.java b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/RequestURLVisitor.java new file mode 100644 index 000000000..f3f4027f4 --- /dev/null +++ b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/RequestURLVisitor.java @@ -0,0 +1,127 @@ +/* + * 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.server.core; + +import org.apache.olingo.commons.api.edm.EdmEntityType; +import org.apache.olingo.server.api.uri.UriInfo; +import org.apache.olingo.server.api.uri.UriInfoAll; +import org.apache.olingo.server.api.uri.UriInfoBatch; +import org.apache.olingo.server.api.uri.UriInfoCrossjoin; +import org.apache.olingo.server.api.uri.UriInfoEntityId; +import org.apache.olingo.server.api.uri.UriInfoMetadata; +import org.apache.olingo.server.api.uri.UriInfoResource; +import org.apache.olingo.server.api.uri.UriInfoService; +import org.apache.olingo.server.api.uri.UriResourceAction; +import org.apache.olingo.server.api.uri.UriResourceComplexProperty; +import org.apache.olingo.server.api.uri.UriResourceCount; +import org.apache.olingo.server.api.uri.UriResourceEntitySet; +import org.apache.olingo.server.api.uri.UriResourceFunction; +import org.apache.olingo.server.api.uri.UriResourceIt; +import org.apache.olingo.server.api.uri.UriResourceLambdaAll; +import org.apache.olingo.server.api.uri.UriResourceLambdaAny; +import org.apache.olingo.server.api.uri.UriResourceLambdaVariable; +import org.apache.olingo.server.api.uri.UriResourceNavigation; +import org.apache.olingo.server.api.uri.UriResourcePrimitiveProperty; +import org.apache.olingo.server.api.uri.UriResourceRef; +import org.apache.olingo.server.api.uri.UriResourceRoot; +import org.apache.olingo.server.api.uri.UriResourceSingleton; +import org.apache.olingo.server.api.uri.UriResourceValue; +import org.apache.olingo.server.api.uri.queryoption.CountOption; +import org.apache.olingo.server.api.uri.queryoption.ExpandOption; +import org.apache.olingo.server.api.uri.queryoption.FilterOption; +import org.apache.olingo.server.api.uri.queryoption.FormatOption; +import org.apache.olingo.server.api.uri.queryoption.IdOption; +import org.apache.olingo.server.api.uri.queryoption.OrderByOption; +import org.apache.olingo.server.api.uri.queryoption.SearchOption; +import org.apache.olingo.server.api.uri.queryoption.SelectOption; +import org.apache.olingo.server.api.uri.queryoption.SkipOption; +import org.apache.olingo.server.api.uri.queryoption.SkipTokenOption; +import org.apache.olingo.server.api.uri.queryoption.TopOption; + +public interface RequestURLVisitor { + + void visit(UriInfo info); + + void visit(UriInfoService info); + + void visit(UriInfoAll info); + + void visit(UriInfoBatch info); + + void visit(UriInfoCrossjoin info); + + void visit(UriInfoEntityId info); + + void visit(UriInfoMetadata info); + + void visit(UriInfoResource info); + + // Walk UriInfoResource + void visit(ExpandOption option); + + void visit(FilterOption info); + + void visit(FormatOption info); + + void visit(IdOption info, EdmEntityType type); + + void visit(CountOption info); + + void visit(OrderByOption option); + + void visit(SearchOption option); + + void visit(SelectOption option); + + void visit(SkipOption option); + + void visit(SkipTokenOption option); + + void visit(TopOption option); + + void visit(UriResourceCount option); + + void visit(UriResourceRef info); + + void visit(UriResourceRoot info); + + void visit(UriResourceValue info); + + void visit(UriResourceAction info); + + void visit(UriResourceEntitySet info); + + void visit(UriResourceFunction info); + + void visit(UriResourceIt info); + + void visit(UriResourceLambdaAll info); + + void visit(UriResourceLambdaAny info); + + void visit(UriResourceLambdaVariable info); + + void visit(UriResourceNavigation info); + + void visit(UriResourceSingleton info); + + void visit(UriResourceComplexProperty info); + + void visit(UriResourcePrimitiveProperty info); +} diff --git a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/ReturnRepresentation.java b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/ReturnRepresentation.java new file mode 100644 index 000000000..e9a213ec3 --- /dev/null +++ b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/ReturnRepresentation.java @@ -0,0 +1,23 @@ +/* + * 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.server.core; + +public enum ReturnRepresentation { + REPRESENTATION, MINIMAL +} \ No newline at end of file diff --git a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/SchemaBasedEdmProvider.java b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/SchemaBasedEdmProvider.java new file mode 100644 index 000000000..8641a6941 --- /dev/null +++ b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/SchemaBasedEdmProvider.java @@ -0,0 +1,315 @@ +/* + * 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.server.core; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.apache.olingo.commons.api.ODataException; +import org.apache.olingo.commons.api.edm.FullQualifiedName; +import org.apache.olingo.commons.api.edm.provider.Action; +import org.apache.olingo.commons.api.edm.provider.ActionImport; +import org.apache.olingo.commons.api.edm.provider.AliasInfo; +import org.apache.olingo.commons.api.edm.provider.Annotatable; +import org.apache.olingo.commons.api.edm.provider.Annotations; +import org.apache.olingo.commons.api.edm.provider.ComplexType; +import org.apache.olingo.commons.api.edm.provider.EdmProvider; +import org.apache.olingo.commons.api.edm.provider.EntityContainer; +import org.apache.olingo.commons.api.edm.provider.EntityContainerInfo; +import org.apache.olingo.commons.api.edm.provider.EntitySet; +import org.apache.olingo.commons.api.edm.provider.EntityType; +import org.apache.olingo.commons.api.edm.provider.EnumType; +import org.apache.olingo.commons.api.edm.provider.Function; +import org.apache.olingo.commons.api.edm.provider.FunctionImport; +import org.apache.olingo.commons.api.edm.provider.Schema; +import org.apache.olingo.commons.api.edm.provider.Singleton; +import org.apache.olingo.commons.api.edm.provider.Term; +import org.apache.olingo.commons.api.edm.provider.TypeDefinition; + +public class SchemaBasedEdmProvider implements EdmProvider { + private final List edmSchemas = new ArrayList(); + + protected void addSchema(Schema schema) { + this.edmSchemas.add(schema); + } + + private Schema getSchema(String ns) { + for (Schema s : this.edmSchemas) { + if (s.getNamespace().equals(ns)) { + return s; + } + } + return null; + } + + @Override + public EnumType getEnumType(FullQualifiedName fqn) throws ODataException { + Schema schema = getSchema(fqn.getNamespace()); + if (schema != null) { + List types = schema.getEnumTypes(); + if (types != null) { + for (EnumType type : types) { + if (type.getName().equals(fqn.getName())) { + return type; + } + } + } + } + return null; + } + + @Override + public TypeDefinition getTypeDefinition(FullQualifiedName fqn) throws ODataException { + Schema schema = getSchema(fqn.getNamespace()); + if (schema != null) { + List types = schema.getTypeDefinitions(); + if (types != null) { + for (TypeDefinition type : types) { + if (type.getName().equals(fqn.getName())) { + return type; + } + } + } + } + return null; + } + + @Override + public List getFunctions(FullQualifiedName fqn) throws ODataException { + ArrayList foundFuncs = new ArrayList(); + Schema schema = getSchema(fqn.getNamespace()); + if (schema != null) { + List functions = schema.getFunctions(); + if (functions != null) { + for (Function func : functions) { + if (func.getName().equals(fqn.getName())) { + foundFuncs.add(func); + } + } + } + } + return foundFuncs; + } + + @Override + public Term getTerm(FullQualifiedName fqn) throws ODataException { + Schema schema = getSchema(fqn.getNamespace()); + if (schema != null) { + List terms = schema.getTerms(); + if (terms != null) { + for (Term term : terms) { + if (term.getName().equals(fqn.getName())) { + return term; + } + } + } + } + return null; + } + + @Override + public EntitySet getEntitySet(FullQualifiedName fqn, String entitySetName) throws ODataException { + Schema schema = getSchema(fqn.getFullQualifiedNameAsString()); + if (schema != null) { + EntityContainer ec = schema.getEntityContainer(); + if (ec != null && ec.getEntitySets() != null) { + for (EntitySet es : ec.getEntitySets()) { + if (es.getName().equals(entitySetName)) { + return es; + } + } + } + } + return null; + } + + @Override + public Singleton getSingleton(FullQualifiedName fqn, String singletonName) throws ODataException { + Schema schema = getSchema(fqn.getFullQualifiedNameAsString()); + if (schema != null) { + EntityContainer ec = schema.getEntityContainer(); + if (ec != null && ec.getSingletons() != null) { + for (Singleton es : ec.getSingletons()) { + if (es.getName().equals(singletonName)) { + return es; + } + } + } + } + return null; + } + + @Override + public ActionImport getActionImport(FullQualifiedName fqn, String actionImportName) + throws ODataException { + Schema schema = getSchema(fqn.getFullQualifiedNameAsString()); + if (schema != null) { + EntityContainer ec = schema.getEntityContainer(); + if (ec != null && ec.getActionImports() != null) { + for (ActionImport es : ec.getActionImports()) { + if (es.getName().equals(actionImportName)) { + return es; + } + } + } + } + return null; + } + + @Override + public FunctionImport getFunctionImport(FullQualifiedName fqn, String functionImportName) + throws ODataException { + Schema schema = getSchema(fqn.getFullQualifiedNameAsString()); + if (schema != null) { + EntityContainer ec = schema.getEntityContainer(); + if (ec != null && ec.getFunctionImports() != null) { + for (FunctionImport es : ec.getFunctionImports()) { + if (es.getName().equals(functionImportName)) { + return es; + } + } + } + } + return null; + } + + @Override + public EntityContainerInfo getEntityContainerInfo(FullQualifiedName fqn) throws ODataException { + Schema schema = null; + + if (fqn == null) { + for (Schema s : this.edmSchemas) { + if (s.getEntityContainer() != null) { + schema = s; + break; + } + } + } else { + schema = getSchema(fqn.getFullQualifiedNameAsString()); + } + + if (schema != null) { + EntityContainer ec = schema.getEntityContainer(); + if (ec != null) { + EntityContainerInfo info = new EntityContainerInfo(); + info.setContainerName(new FullQualifiedName(schema.getNamespace())); + if (schema.getEntityContainer().getExtendsContainer() != null) { + info.setExtendsContainer(new FullQualifiedName(schema.getEntityContainer().getExtendsContainer())); + } + return info; + } + } + return null; + } + + @Override + public List getAliasInfos() throws ODataException { + Schema schema = null; + for (Schema s : this.edmSchemas) { + if (s.getEntityContainer() != null) { + schema = s; + break; + } + } + + if (schema == null) { + schema = this.edmSchemas.get(0); + } + + AliasInfo ai = new AliasInfo(); + ai.setAlias(schema.getAlias()); + ai.setNamespace(schema.getNamespace()); + return Arrays.asList(ai); + } + + @Override + public EntityContainer getEntityContainer() throws ODataException { + // note that there can be many schemas, but only one needs to contain the + // entity container in a given metadata document. + for (Schema s : this.edmSchemas) { + if (s.getEntityContainer() != null) { + return s.getEntityContainer(); + } + } + return null; + } + + @Override + public List getSchemas() throws ODataException { + return new ArrayList(this.edmSchemas); + } + + @Override + public EntityType getEntityType(final FullQualifiedName fqn) throws ODataException { + Schema schema = getSchema(fqn.getNamespace()); + if (schema != null) { + if (schema.getEntityTypes() != null) { + for (EntityType type : schema.getEntityTypes()) { + if (type.getName().equals(fqn.getName())) { + return type; + } + } + } + } + return null; + } + + @Override + public ComplexType getComplexType(final FullQualifiedName fqn) throws ODataException { + Schema schema = getSchema(fqn.getNamespace()); + if (schema != null) { + if (schema.getComplexTypes() != null) { + for (ComplexType type : schema.getComplexTypes()) { + if (type.getName().equals(fqn.getName())) { + return type; + } + } + } + } + return null; + } + + @Override + public List getActions(final FullQualifiedName fqn) throws ODataException { + ArrayList actions = new ArrayList(); + Schema schema = getSchema(fqn.getNamespace()); + if (schema != null) { + List types = schema.getActions(); + if (types != null) { + for (Action type : types) { + if (type.getName().equals(fqn.getName())) { + actions.add(type); + } + } + } + } + return actions; + } + + @Override + public Annotations getAnnotationsGroup(FullQualifiedName targetName) throws ODataException { + return null; + } + + @Override + public Annotatable getAnnoatatable(FullQualifiedName annotatedName) throws ODataException { + return null; + } +} diff --git a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/ServiceDispatcher.java b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/ServiceDispatcher.java new file mode 100644 index 000000000..839d87780 --- /dev/null +++ b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/ServiceDispatcher.java @@ -0,0 +1,227 @@ +/* + * 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.server.core; + +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; + +import org.apache.olingo.commons.api.format.ContentType; +import org.apache.olingo.server.api.OData; +import org.apache.olingo.server.api.ODataApplicationException; +import org.apache.olingo.server.api.ODataRequest; +import org.apache.olingo.server.api.ODataResponse; +import org.apache.olingo.server.api.ODataTranslatedException; +import org.apache.olingo.server.api.ServiceMetadata; +import org.apache.olingo.server.api.serializer.CustomContentTypeSupport; +import org.apache.olingo.server.api.uri.UriInfo; +import org.apache.olingo.server.api.uri.UriInfoBatch; +import org.apache.olingo.server.api.uri.UriInfoCrossjoin; +import org.apache.olingo.server.api.uri.UriInfoEntityId; +import org.apache.olingo.server.api.uri.UriInfoMetadata; +import org.apache.olingo.server.api.uri.UriInfoService; +import org.apache.olingo.server.api.uri.UriResourceAction; +import org.apache.olingo.server.api.uri.UriResourceComplexProperty; +import org.apache.olingo.server.api.uri.UriResourceCount; +import org.apache.olingo.server.api.uri.UriResourceEntitySet; +import org.apache.olingo.server.api.uri.UriResourceFunction; +import org.apache.olingo.server.api.uri.UriResourceNavigation; +import org.apache.olingo.server.api.uri.UriResourcePrimitiveProperty; +import org.apache.olingo.server.api.uri.UriResourceRef; +import org.apache.olingo.server.api.uri.UriResourceSingleton; +import org.apache.olingo.server.api.uri.UriResourceValue; +import org.apache.olingo.server.core.requests.ActionRequest; +import org.apache.olingo.server.core.requests.BatchRequest; +import org.apache.olingo.server.core.requests.DataRequest; +import org.apache.olingo.server.core.requests.FunctionRequest; +import org.apache.olingo.server.core.requests.MediaRequest; +import org.apache.olingo.server.core.requests.MetadataRequest; +import org.apache.olingo.server.core.requests.ServiceDocumentRequest; +import org.apache.olingo.server.core.uri.parser.Parser; +import org.apache.olingo.server.core.uri.validator.UriValidator; + +public class ServiceDispatcher extends RequestURLHierarchyVisitor { + private final OData odata; + protected ServiceMetadata metadata; + protected ServiceHandler handler; + protected CustomContentTypeSupport customContentSupport; + private String idOption; + protected ServiceRequest request; + + public ServiceDispatcher(OData odata, ServiceMetadata metadata, ServiceHandler handler, + CustomContentTypeSupport customContentSupport) { + this.odata = odata; + this.metadata = metadata; + this.handler = handler; + this.customContentSupport = customContentSupport; + } + + public void execute(ODataRequest odRequest, ODataResponse odResponse) + throws ODataTranslatedException, ODataApplicationException { + + UriInfo uriInfo = new Parser().parseUri(odRequest.getRawODataPath(), odRequest.getRawQueryPath(), null, + this.metadata.getEdm()); + + new UriValidator().validate(uriInfo, odRequest.getMethod()); + + visit(uriInfo); + + // this should cover for any unsupported calls until they are implemented + if (this.request == null) { + this.request = new ServiceRequest(this.odata, this.metadata) { + @Override + public ContentType getResponseContentType() throws ContentNegotiatorException { + return ContentType.APPLICATION_JSON; + } + + @Override + public void execute(ServiceHandler handler, ODataResponse response) + throws ODataTranslatedException, ODataApplicationException { + handler.anyUnsupported(getODataRequest(), response); + } + }; + } + + // To handle $entity?$id=http://localhost/EntitySet(key) as + // http://localhost/EntitySet(key) + if (this.idOption != null) { + try { + this.request = this.request.parseLink(new URI(this.idOption)); + } catch (URISyntaxException e) { + throw new ODataHandlerException("Invalid $id value", + ODataHandlerException.MessageKeys.FUNCTIONALITY_NOT_IMPLEMENTED, this.idOption); + } + } + + this.request.setODataRequest(odRequest); + this.request.setUriInfo(uriInfo); + this.request.setCustomContentTypeSupport(this.customContentSupport); + this.request.execute(this.handler, odResponse); + } + + @Override + public void visit(UriInfoMetadata info) { + this.request = new MetadataRequest(this.odata, this.metadata); + } + + @Override + public void visit(UriInfoService info) { + this.request = new ServiceDocumentRequest(this.odata, this.metadata); + } + + @Override + public void visit(UriResourceEntitySet info) { + DataRequest dataRequest = new DataRequest(this.odata, this.metadata); + dataRequest.setUriResourceEntitySet(info); + this.request = dataRequest; + } + + @Override + public void visit(UriResourceCount option) { + DataRequest dataRequest = (DataRequest) this.request; + dataRequest.setCountRequest(option != null); + } + + @Override + public void visit(UriResourceComplexProperty info) { + DataRequest dataRequest = (DataRequest) this.request; + dataRequest.setUriResourceProperty(info); + } + + @Override + public void visit(UriResourcePrimitiveProperty info) { + DataRequest dataRequest = (DataRequest) this.request; + dataRequest.setUriResourceProperty(info); + } + + @Override + public void visit(UriResourceValue info) { + DataRequest dataRequest = (DataRequest) this.request; + if (dataRequest.isPropertyRequest()) { + dataRequest.setValueRequest(info != null); + } else { + MediaRequest mediaRequest = new MediaRequest(this.odata, this.metadata); + mediaRequest.setUriResourceEntitySet(dataRequest.getUriResourceEntitySet()); + this.request = mediaRequest; + } + } + + @Override + public void visit(UriResourceAction info) { + ActionRequest actionRequest = new ActionRequest(this.odata, this.metadata); + actionRequest.setUriResourceAction(info); + this.request = actionRequest; + } + + @Override + public void visit(UriResourceFunction info) { + FunctionRequest functionRequest = new FunctionRequest(this.odata, this.metadata); + functionRequest.setUriResourceFunction(info); + this.request = functionRequest; + } + + @Override + public void visit(UriResourceNavigation info) { + DataRequest dataRequest = (DataRequest) this.request; + dataRequest.addUriResourceNavigation(info); + } + + @Override + public void visit(UriResourceRef info) { + // this is same as data, but return is just entity references. + DataRequest dataRequest = (DataRequest) this.request; + dataRequest.setReferenceRequest(info != null); + } + + @Override + public void visit(UriInfoBatch info) { + this.request = new BatchRequest(this.odata, this.metadata); + } + + @Override + public void visit(UriResourceSingleton info) { + DataRequest dataRequest = new DataRequest(this.odata, this.metadata); + dataRequest.setUriResourceSingleton(info); + this.request = dataRequest; + } + + @Override + public void visit(UriInfoEntityId info) { + DataRequest dataRequest = new DataRequest(this.odata, this.metadata); + this.request = dataRequest; + + // this can relative or absolute form + String id = info.getIdOption().getValue(); + try { + URL url = new URL(id); + this.idOption = url.getPath(); + } catch (MalformedURLException e) { + this.idOption = id; + } + super.visit(info); + } + + @Override + public void visit(UriInfoCrossjoin info) { + DataRequest dataRequest = new DataRequest(this.odata, this.metadata); + dataRequest.setCrossJoin(info); + this.request = dataRequest; + } +} diff --git a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/ServiceHandler.java b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/ServiceHandler.java new file mode 100644 index 000000000..8a9c6108b --- /dev/null +++ b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/ServiceHandler.java @@ -0,0 +1,263 @@ +/* + * 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.server.core; + +import java.io.InputStream; +import java.net.URI; +import java.util.List; + +import org.apache.olingo.commons.api.data.Entity; +import org.apache.olingo.commons.api.data.Property; +import org.apache.olingo.commons.api.http.HttpMethod; +import org.apache.olingo.server.api.ODataApplicationException; +import org.apache.olingo.server.api.ODataRequest; +import org.apache.olingo.server.api.ODataResponse; +import org.apache.olingo.server.api.ODataTranslatedException; +import org.apache.olingo.server.api.processor.Processor; +import org.apache.olingo.server.core.requests.ActionRequest; +import org.apache.olingo.server.core.requests.DataRequest; +import org.apache.olingo.server.core.requests.FunctionRequest; +import org.apache.olingo.server.core.requests.MediaRequest; +import org.apache.olingo.server.core.requests.MetadataRequest; +import org.apache.olingo.server.core.requests.ServiceDocumentRequest; +import org.apache.olingo.server.core.responses.EntityResponse; +import org.apache.olingo.server.core.responses.MetadataResponse; +import org.apache.olingo.server.core.responses.NoContentResponse; +import org.apache.olingo.server.core.responses.PropertyResponse; +import org.apache.olingo.server.core.responses.ServiceDocumentResponse; +import org.apache.olingo.server.core.responses.ServiceResponse; +import org.apache.olingo.server.core.responses.StreamResponse; + +public interface ServiceHandler extends Processor { + + /** + * Read CSDL document of the Service + * @param request + * @param response + * @throws ODataTranslatedException + * @throws ODataApplicationException + */ + void readMetadata(MetadataRequest request, MetadataResponse response) + throws ODataTranslatedException, ODataApplicationException; + + /** + * Read ServiceDocument of the service + * @param request + * @param response + * @throws ODataTranslatedException + * @throws ODataApplicationException + */ + void readServiceDocument(ServiceDocumentRequest request, ServiceDocumentResponse response) + throws ODataTranslatedException, ODataApplicationException; + + /** + * Read operation for EntitySets, Entities, Properties, Media etc. Based on the type of request + * the response object is different. Even the navigation based queries are handled by this method. + * @param request + * @param response + * @throws ODataTranslatedException + * @throws ODataApplicationException + */ + void read(DataRequest request, T response) + throws ODataTranslatedException, ODataApplicationException; + + /** + * Create new entity in the service based on the entity object provided + * @param request + * @param entity + * @param response + * @throws ODataTranslatedException + * @throws ODataApplicationException + */ + void createEntity(DataRequest request, Entity entity, EntityResponse response) + throws ODataTranslatedException, ODataApplicationException; + + /** + * Update the entity object. + * @param request + * @param entity + * @param merge - true if merge operation, false it needs to be replaced + * @param entityETag - previous entity tag if provided by the user. "*" means allow. + * @param response + * @throws ODataTranslatedException + * @throws ODataApplicationException + */ + void updateEntity(DataRequest request, Entity entity, boolean merge, String entityETag, + EntityResponse response) throws ODataTranslatedException, ODataApplicationException; + + /** + * Delete the Entity + * @param request + * @param entityETag - entity tag to match, if provided by the user. "*" means allow + * @param response + * @throws ODataTranslatedException + * @throws ODataApplicationException + */ + void deleteEntity(DataRequest request, String entityETag, EntityResponse response) + throws ODataTranslatedException, ODataApplicationException; + + /** + * Update a non-media/stream property.if the value of property NULL, it should be treated as + * DeleteProperty 11.4.9.2 + * @param request + * @param property - Updated property. + * @param merge - if the property is complex, true here means merge, false is replace + * @param entityETag - entity tag to match before update operation, "*" allows all. + * @param response + * @throws ODataTranslatedException + * @throws ODataApplicationException + */ + void updateProperty(DataRequest request, Property property, boolean merge, String entityETag, + PropertyResponse response) throws ODataTranslatedException, ODataApplicationException; + + /** + * Update Stream property, if StreamContent is null, it should treated as delete request + * @param request + * @param entityETag - entity tag to match before update operation, "*" allows all. + * @param streamContent - updated stream content + * @param response + * @throws ODataTranslatedException + * @throws ODataApplicationException + */ + void upsertStreamProperty(DataRequest request, String entityETag, InputStream streamContent, + NoContentResponse response) throws ODataTranslatedException, ODataApplicationException; + + /** + * Invocation of a Function. The response object will be based on metadata defined for service + * @param request + * @param method + * @param response + * @throws ODataTranslatedException + * @throws ODataApplicationException + */ + void invoke(FunctionRequest request, HttpMethod method, T response) + throws ODataTranslatedException, ODataApplicationException; + + /** + * Invocation of a Function. The response object will be based on metadata defined for service + * @param request + * @param eTag + * @param response + * @throws ODataTranslatedException + * @throws ODataApplicationException + */ + void invoke(ActionRequest request, String eTag, T response) + throws ODataTranslatedException, ODataApplicationException; + + /** + * Read media stream content of a Entity + * @param request + * @param response + * @throws ODataTranslatedException + * @throws ODataApplicationException + */ + void readMediaStream(MediaRequest request, StreamResponse response) + throws ODataTranslatedException, ODataApplicationException; + + /** + * Update of Media Stream Content of a Entity. If the mediaContent is null it should be treated + * as delete request. + * @param request + * @param entityETag - entity etag to match before update operation, "*" allows all. + * @param mediaContent - if null, must be treated as delete request + * @param response + * @throws ODataTranslatedException + * @throws ODataApplicationException + */ + void upsertMediaStream(MediaRequest request, String entityETag, InputStream mediaContent, + NoContentResponse response) throws ODataTranslatedException, ODataApplicationException; + + /** + * Any Unsupported one will be directed here. + * @param request + * @param response + * @throws ODataTranslatedException + * @throws ODataApplicationException + */ + void anyUnsupported(ODataRequest request, ODataResponse response) + throws ODataTranslatedException, ODataApplicationException; + + /** + * Add references (relationships) to Entity. + * @param request + * @param entityETag - entity etag to match before add operation, "*" allows all. + * @param idReferences - references to add + * @param response - return always should be 204 + * @throws ODataTranslatedException + * @throws ODataApplicationException + */ + void addReference(DataRequest request, String entityETag, List idReferences, NoContentResponse response) + throws ODataTranslatedException, ODataApplicationException; + + /** + * Update references (relationships) in an Entity + * @param request + * @param entityETag + * @param referenceId + * @param response - always should be 204 + * @throws ODataTranslatedException + * @throws ODataApplicationException + */ + void updateReference(DataRequest request, String entityETag, URI referenceId, NoContentResponse response) + throws ODataTranslatedException, ODataApplicationException; + + /** + * Delete references (relationships) in an Entity + * @param request + * @param deleteId + * @param entityETag + * @param response - always should be 204 + * @throws ODataTranslatedException + * @throws ODataApplicationException + */ + void deleteReference(DataRequest request, URI deleteId, String entityETag, NoContentResponse response) + throws ODataTranslatedException, ODataApplicationException; + + + /** + * During a batch operation, this method starts the transaction (if any) before any operation is handled + * by the service. No nested transactions. + * @return must return a unique transaction id that references a atomic operation. + */ + String startTransaction(); + + /** + * When a batch operation is complete and all the intermediate service requests are successful, then + * commit is called with transaction id returned in the startTransaction method. + * @param txnId + */ + void commit(String txnId); + /** + * When a batch operation is in-complete due to an error in the middle of changeset, then rollback is + * called with transaction id, that returned from startTransaction method. + * @param txnId + */ + void rollback(String txnId); + + /** + * This is not complete, more URL parsing changes required. Cross join between two entities. + * @param dataRequest + * @param entitySetNames + * @param response + * @throws ODataTranslatedException + * @throws ODataApplicationException + */ + void crossJoin(DataRequest dataRequest, List entitySetNames, ODataResponse response) + throws ODataTranslatedException, ODataApplicationException; +} diff --git a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/ServiceRequest.java b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/ServiceRequest.java new file mode 100644 index 000000000..e9a8cfe4f --- /dev/null +++ b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/ServiceRequest.java @@ -0,0 +1,253 @@ +/* + * 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.server.core; + +import java.net.URI; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; + +import org.apache.olingo.commons.api.data.ContextURL; +import org.apache.olingo.commons.api.format.ContentType; +import org.apache.olingo.commons.api.format.ODataFormat; +import org.apache.olingo.commons.api.http.HttpHeader; +import org.apache.olingo.commons.api.http.HttpMethod; +import org.apache.olingo.server.api.OData; +import org.apache.olingo.server.api.ODataApplicationException; +import org.apache.olingo.server.api.ODataRequest; +import org.apache.olingo.server.api.ODataResponse; +import org.apache.olingo.server.api.ODataTranslatedException; +import org.apache.olingo.server.api.ServiceMetadata; +import org.apache.olingo.server.api.serializer.ComplexSerializerOptions; +import org.apache.olingo.server.api.serializer.CustomContentTypeSupport; +import org.apache.olingo.server.api.serializer.EntityCollectionSerializerOptions; +import org.apache.olingo.server.api.serializer.EntitySerializerOptions; +import org.apache.olingo.server.api.serializer.ODataSerializer; +import org.apache.olingo.server.api.serializer.SerializerException; +import org.apache.olingo.server.api.uri.UriInfo; +import org.apache.olingo.server.core.requests.DataRequest; +import org.apache.olingo.server.core.uri.parser.Parser; +import org.apache.olingo.server.core.uri.parser.UriParserException; + +public abstract class ServiceRequest { + protected OData odata; + protected UriInfo uriInfo; + protected ServiceMetadata serviceMetadata; + protected CustomContentTypeSupport customContentType; + protected ODataRequest request; + + public ServiceRequest(OData odata, ServiceMetadata serviceMetadata) { + this.odata = odata; + this.serviceMetadata = serviceMetadata; + } + + public OData getOdata() { + return odata; + } + + public ServiceMetadata getServiceMetaData() { + return this.serviceMetadata; + } + + public UriInfo getUriInfo() { + return uriInfo; + } + + protected void setUriInfo(UriInfo uriInfo) { + this.uriInfo = uriInfo; + } + + public boolean allowedMethod() { + return isGET(); + } + + public CustomContentTypeSupport getCustomContentTypeSupport() { + return this.customContentType; + } + + public void setCustomContentTypeSupport(CustomContentTypeSupport support) { + this.customContentType = support; + } + + public ODataRequest getODataRequest() { + return this.request; + } + + protected void setODataRequest(ODataRequest request) { + this.request = request; + } + + public ContentType getRequestContentType() { + if (this.request.getHeader(HttpHeader.CONTENT_TYPE) != null) { + return ContentType.parse(this.request.getHeader(HttpHeader.CONTENT_TYPE)); + } + return ContentType.APPLICATION_OCTET_STREAM; + } + + public abstract void execute(ServiceHandler handler, ODataResponse response) + throws ODataTranslatedException, ODataApplicationException; + + public abstract ContentType getResponseContentType() throws ContentNegotiatorException; + + public void methodNotAllowed() throws ODataHandlerException { + throw new ODataHandlerException("HTTP method " + this.request.getMethod() + " is not allowed.", + ODataHandlerException.MessageKeys.HTTP_METHOD_NOT_ALLOWED, this.request.getMethod() + .toString()); + } + + public void notImplemented() throws ODataHandlerException { + throw new ODataHandlerException("not implemented", //$NON-NLS-1$ + ODataHandlerException.MessageKeys.FUNCTIONALITY_NOT_IMPLEMENTED); + } + + protected boolean isGET() { + return this.request.getMethod() == HttpMethod.GET; + } + + protected boolean isPUT() { + return this.request.getMethod() == HttpMethod.PUT; + } + + protected boolean isDELETE() { + return this.request.getMethod() == HttpMethod.DELETE; + } + + protected boolean isPATCH() { + return this.request.getMethod() == HttpMethod.PATCH; + } + + protected boolean isPOST() { + return this.request.getMethod() == HttpMethod.POST; + } + + public T getSerializerOptions(Class serilizerOptions, ContextURL contextUrl, + boolean references) throws ContentNegotiatorException { + final ODataFormat format = ODataFormat.fromContentType(getResponseContentType()); + + if (serilizerOptions.isAssignableFrom(EntitySerializerOptions.class)) { + return (T) EntitySerializerOptions.with() + .contextURL(format == ODataFormat.JSON_NO_METADATA ? null : contextUrl) + .expand(uriInfo.getExpandOption()).select(this.uriInfo.getSelectOption()) + .setWriteOnlyReferences(references).build(); + } else if (serilizerOptions.isAssignableFrom(EntityCollectionSerializerOptions.class)) { + return (T) EntityCollectionSerializerOptions.with() + .contextURL(format == ODataFormat.JSON_NO_METADATA ? null : contextUrl) + .count(uriInfo.getCountOption()).expand(uriInfo.getExpandOption()) + .select(uriInfo.getSelectOption()).setWriteOnlyReferences(references).build(); + } else if (serilizerOptions.isAssignableFrom(ComplexSerializerOptions.class)) { + return (T) ComplexSerializerOptions.with().contextURL(contextUrl) + .expand(this.uriInfo.getExpandOption()).select(this.uriInfo.getSelectOption()).build(); + } + return null; + } + + public ReturnRepresentation getReturnRepresentation() { + String prefer = this.request.getHeader(HttpHeader.PREFER); + if (prefer == null) { + return ReturnRepresentation.REPRESENTATION; + } + if (prefer.contains("return=minimal")) { //$NON-NLS-1$ + return ReturnRepresentation.MINIMAL; + } + return ReturnRepresentation.REPRESENTATION; + } + + public String getHeader(String key) { + return this.request.getHeader(key); + } + + public String getETag() { + String etag = getHeader(HttpHeader.IF_MATCH); + if (etag == null) { + etag = getHeader(HttpHeader.IF_NONE_MATCH); + } + return ((etag == null) ? "*" : etag); //$NON-NLS-1$ + } + + public ODataSerializer getSerializer() throws ContentNegotiatorException, + SerializerException { + ODataFormat format = ODataFormat.fromContentType(getResponseContentType()); + return this.odata.createSerializer(format); + } + + public Map getPreferences(){ + HashMap map = new HashMap(); + List headers = request.getHeaders(HttpHeader.PREFER); + if (headers != null) { + for (String header:headers) { + int idx = header.indexOf('='); + if (idx != -1) { + String key = header.substring(0, idx); + String value = header.substring(idx+1); + if (value.startsWith("\"")) { + value = value.substring(1); + } + if (value.endsWith("\"")) { + value = value.substring(0, value.length()-1); + } + map.put(key, value); + } else { + map.put(header, "true"); + } + } + } + return map; + } + + public String getPreference(String key) { + return getPreferences().get(key); + } + + public String getQueryParameter(String param) { + String queryPath = getODataRequest().getRawQueryPath(); + if (queryPath != null) { + StringTokenizer st = new StringTokenizer(queryPath, ","); + while (st.hasMoreTokens()) { + String token = st.nextToken(); + int index = token.indexOf('='); + if (index != -1) { + String key = token.substring(0, index); + String value = token.substring(index+1); + if (key.equals(param)) { + return value; + } + } + } + } + return null; + } + + public DataRequest parseLink(URI uri) throws UriParserException { + String rawPath = uri.getPath(); + int e = rawPath.indexOf("/", 1); + if (-1 == e) { + rawPath = uri.getPath(); + } else { + rawPath = rawPath.substring(e); + } + + UriInfo uriInfo = new Parser().parseUri(rawPath, uri.getQuery(), null, + this.serviceMetadata.getEdm()); + ServiceDispatcher dispatcher = new ServiceDispatcher(odata, serviceMetadata, null, customContentType); + dispatcher.visit(uriInfo); + return (DataRequest)dispatcher.request; + } +} diff --git a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/legacy/ProcessorServiceHandler.java b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/legacy/ProcessorServiceHandler.java new file mode 100644 index 000000000..fa8f44557 --- /dev/null +++ b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/legacy/ProcessorServiceHandler.java @@ -0,0 +1,433 @@ +/* + * 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.server.core.legacy; + +import java.io.InputStream; +import java.net.URI; +import java.util.LinkedList; +import java.util.List; + +import org.apache.olingo.commons.api.data.Entity; +import org.apache.olingo.commons.api.data.Property; +import org.apache.olingo.commons.api.edm.EdmProperty; +import org.apache.olingo.commons.api.http.HttpMethod; +import org.apache.olingo.server.api.OData; +import org.apache.olingo.server.api.ODataApplicationException; +import org.apache.olingo.server.api.ODataRequest; +import org.apache.olingo.server.api.ODataResponse; +import org.apache.olingo.server.api.ODataTranslatedException; +import org.apache.olingo.server.api.ServiceMetadata; +import org.apache.olingo.server.api.processor.ComplexCollectionProcessor; +import org.apache.olingo.server.api.processor.ComplexProcessor; +import org.apache.olingo.server.api.processor.CountComplexCollectionProcessor; +import org.apache.olingo.server.api.processor.CountEntityCollectionProcessor; +import org.apache.olingo.server.api.processor.CountPrimitiveCollectionProcessor; +import org.apache.olingo.server.api.processor.EntityCollectionProcessor; +import org.apache.olingo.server.api.processor.EntityProcessor; +import org.apache.olingo.server.api.processor.MediaEntityProcessor; +import org.apache.olingo.server.api.processor.MetadataProcessor; +import org.apache.olingo.server.api.processor.PrimitiveCollectionProcessor; +import org.apache.olingo.server.api.processor.PrimitiveProcessor; +import org.apache.olingo.server.api.processor.PrimitiveValueProcessor; +import org.apache.olingo.server.api.processor.Processor; +import org.apache.olingo.server.api.processor.ReferenceProcessor; +import org.apache.olingo.server.api.processor.ServiceDocumentProcessor; +import org.apache.olingo.server.core.ODataHandlerException; +import org.apache.olingo.server.core.ServiceHandler; +import org.apache.olingo.server.core.requests.ActionRequest; +import org.apache.olingo.server.core.requests.DataRequest; +import org.apache.olingo.server.core.requests.FunctionRequest; +import org.apache.olingo.server.core.requests.MediaRequest; +import org.apache.olingo.server.core.requests.MetadataRequest; +import org.apache.olingo.server.core.requests.ServiceDocumentRequest; +import org.apache.olingo.server.core.responses.CountResponse; +import org.apache.olingo.server.core.responses.EntityResponse; +import org.apache.olingo.server.core.responses.EntitySetResponse; +import org.apache.olingo.server.core.responses.MetadataResponse; +import org.apache.olingo.server.core.responses.NoContentResponse; +import org.apache.olingo.server.core.responses.PrimitiveValueResponse; +import org.apache.olingo.server.core.responses.PropertyResponse; +import org.apache.olingo.server.core.responses.ServiceDocumentResponse; +import org.apache.olingo.server.core.responses.ServiceResponse; +import org.apache.olingo.server.core.responses.ServiceResponseVisior; +import org.apache.olingo.server.core.responses.StreamResponse; + +public class ProcessorServiceHandler implements ServiceHandler { + private final List processors = new LinkedList(); + private OData odata; + private ServiceMetadata serviceMetadata; + + @Override + public void init(OData odata, ServiceMetadata serviceMetadata) { + this.odata = odata; + this.serviceMetadata = serviceMetadata; + } + + public void register(Processor processor) { + this.processors.add(processor); + processor.init(odata, serviceMetadata); + } + + private T selectProcessor(final Class cls) throws ODataHandlerException { + for (final Processor processor : processors) { + if (cls.isAssignableFrom(processor.getClass())) { + processor.init(odata, serviceMetadata); + return cls.cast(processor); + } + } + throw new ODataHandlerException("Processor: " + cls.getSimpleName() + " not registered.", + ODataHandlerException.MessageKeys.PROCESSOR_NOT_IMPLEMENTED, cls.getSimpleName()); + } + + @Override + public void readMetadata(MetadataRequest request, MetadataResponse response) + throws ODataTranslatedException, ODataApplicationException { + selectProcessor(MetadataProcessor.class).readMetadata(request.getODataRequest(), + response.getODataResponse(), request.getUriInfo(), request.getResponseContentType()); + } + + @Override + public void readServiceDocument(ServiceDocumentRequest request, ServiceDocumentResponse response) + throws ODataTranslatedException, ODataApplicationException { + selectProcessor(ServiceDocumentProcessor.class).readServiceDocument(request.getODataRequest(), + response.getODataResponse(), request.getUriInfo(), request.getResponseContentType()); + } + + @Override + public void read(final DataRequest request, final T response) + throws ODataTranslatedException, ODataApplicationException { + response.accepts(new ServiceResponseVisior() { + @Override + public void visit(CountResponse response) throws ODataTranslatedException, ODataApplicationException { + if (request.getUriResourceProperty() != null) { + EdmProperty edmProperty = request.getUriResourceProperty().getProperty(); + if (edmProperty.isPrimitive()) { + selectProcessor(CountPrimitiveCollectionProcessor.class).countPrimitiveCollection( + request.getODataRequest(), response.getODataResponse(), request.getUriInfo()); + } else { + selectProcessor(CountComplexCollectionProcessor.class).countComplexCollection( + request.getODataRequest(), response.getODataResponse(), request.getUriInfo()); + } + } else { + selectProcessor(CountEntityCollectionProcessor.class).countEntityCollection( + request.getODataRequest(), response.getODataResponse(), request.getUriInfo()); + } + } + + @Override + public void visit(EntityResponse response) throws ODataTranslatedException, + ODataApplicationException { + selectProcessor(EntityProcessor.class).readEntity(request.getODataRequest(), + response.getODataResponse(), request.getUriInfo(), request.getResponseContentType()); + } + + @Override + public void visit(PrimitiveValueResponse response) throws ODataTranslatedException, + ODataApplicationException { + selectProcessor(PrimitiveValueProcessor.class).readPrimitiveValue( + request.getODataRequest(), response.getODataResponse(), request.getUriInfo(), + request.getResponseContentType()); + } + + @Override + public void visit(PropertyResponse response) throws ODataTranslatedException, + ODataApplicationException { + EdmProperty edmProperty = request.getUriResourceProperty().getProperty(); + if (edmProperty.isPrimitive()) { + if(edmProperty.isCollection()) { + selectProcessor(PrimitiveCollectionProcessor.class).readPrimitiveCollection( + request.getODataRequest(), response.getODataResponse(), request.getUriInfo(), + request.getResponseContentType()); + + } else { + selectProcessor(PrimitiveProcessor.class).readPrimitive( + request.getODataRequest(), response.getODataResponse(), request.getUriInfo(), + request.getResponseContentType()); + } + } else { + if(edmProperty.isCollection()) { + selectProcessor(ComplexCollectionProcessor.class).readComplexCollection( + request.getODataRequest(), response.getODataResponse(), request.getUriInfo(), + request.getResponseContentType()); + + } else { + selectProcessor(ComplexProcessor.class).readComplex( + request.getODataRequest(), response.getODataResponse(), request.getUriInfo(), + request.getResponseContentType()); + + } + } + } + + @Override + public void visit(StreamResponse response) throws ODataTranslatedException, + ODataApplicationException { + response.writeServerError(true); + } + + @Override + public void visit(EntitySetResponse response) throws ODataTranslatedException, + ODataApplicationException { + selectProcessor(EntityCollectionProcessor.class).readEntityCollection(request.getODataRequest(), + response.getODataResponse(), request.getUriInfo(), request.getResponseContentType()); + } + }); + } + + @Override + public void createEntity(DataRequest request, Entity entity, EntityResponse response) + throws ODataTranslatedException, ODataApplicationException { + if (request.getEntitySet().getEntityType().hasStream()) { + selectProcessor(MediaEntityProcessor.class).createMediaEntity( + request.getODataRequest(), response.getODataResponse(), request.getUriInfo(), + request.getRequestContentType(),request.getResponseContentType()); + } else { + selectProcessor(EntityProcessor.class).createEntity(request.getODataRequest(), + response.getODataResponse(), request.getUriInfo(), request.getRequestContentType(), + request.getResponseContentType()); + } + } + + @Override + public void updateEntity(DataRequest request, Entity entity, boolean merge, String entityETag, + EntityResponse response) throws ODataTranslatedException, ODataApplicationException { + if (request.getEntitySet().getEntityType().hasStream()) { + selectProcessor(MediaEntityProcessor.class).updateMediaEntity( + request.getODataRequest(), response.getODataResponse(), request.getUriInfo(), + request.getRequestContentType(),request.getResponseContentType()); + } else { + selectProcessor(EntityProcessor.class).updateEntity(request.getODataRequest(), + response.getODataResponse(), request.getUriInfo(), request.getRequestContentType(), + request.getResponseContentType()); + } + } + + @Override + public void deleteEntity(DataRequest request, String entityETag, EntityResponse response) + throws ODataTranslatedException, ODataApplicationException { + selectProcessor(EntityProcessor.class).deleteEntity(request.getODataRequest(), + response.getODataResponse(), request.getUriInfo()); + } + + @Override + public void updateProperty(DataRequest request, Property property, boolean merge, + String entityETag, PropertyResponse response) throws ODataTranslatedException, + ODataApplicationException { + if (property.isPrimitive()) { + if (property.isCollection()) { + selectProcessor(PrimitiveCollectionProcessor.class).updatePrimitiveCollection( + request.getODataRequest(), response.getODataResponse(), request.getUriInfo(), + request.getRequestContentType(), request.getResponseContentType()); + } else { + selectProcessor(PrimitiveProcessor.class).updatePrimitive( + request.getODataRequest(), response.getODataResponse(), request.getUriInfo(), + request.getRequestContentType(), request.getResponseContentType()); + } + } else { + if (property.isCollection()) { + selectProcessor(ComplexCollectionProcessor.class).updateComplexCollection( + request.getODataRequest(), response.getODataResponse(), request.getUriInfo(), + request.getRequestContentType(), request.getResponseContentType()); + } else { + selectProcessor(ComplexProcessor.class).updateComplex( + request.getODataRequest(), response.getODataResponse(), request.getUriInfo(), + request.getRequestContentType(), request.getResponseContentType()); + } + } + } + + @Override + public void upsertStreamProperty(DataRequest request, String entityETag, + InputStream streamContent, NoContentResponse response) throws ODataTranslatedException, + ODataApplicationException { + throw new ODataHandlerException("not implemented", + ODataHandlerException.MessageKeys.FUNCTIONALITY_NOT_IMPLEMENTED); + } + + @Override + public void invoke(final FunctionRequest request, HttpMethod method, + final T response) throws ODataTranslatedException, ODataApplicationException { + if (method != HttpMethod.GET) { + throw new ODataHandlerException("HTTP method " + method + " is not allowed.", + ODataHandlerException.MessageKeys.HTTP_METHOD_NOT_ALLOWED, method.toString()); + } + + response.accepts(new ServiceResponseVisior() { + @Override + public void visit(EntityResponse response) throws ODataTranslatedException, + ODataApplicationException { + selectProcessor(EntityProcessor.class).readEntity(request.getODataRequest(), + response.getODataResponse(), request.getUriInfo(), request.getResponseContentType()); + } + + @Override + public void visit(PropertyResponse response) throws ODataTranslatedException, + ODataApplicationException { + if (request.isReturnTypePrimitive()) { + if(request.isCollection()) { + selectProcessor(PrimitiveCollectionProcessor.class).readPrimitiveCollection( + request.getODataRequest(), response.getODataResponse(), request.getUriInfo(), + request.getResponseContentType()); + + } else { + selectProcessor(PrimitiveProcessor.class).readPrimitive( + request.getODataRequest(), response.getODataResponse(), request.getUriInfo(), + request.getResponseContentType()); + } + } else { + if(request.isCollection()) { + selectProcessor(ComplexCollectionProcessor.class).readComplexCollection( + request.getODataRequest(), response.getODataResponse(), request.getUriInfo(), + request.getResponseContentType()); + + } else { + selectProcessor(ComplexProcessor.class).readComplex( + request.getODataRequest(), response.getODataResponse(), request.getUriInfo(), + request.getResponseContentType()); + } + } + } + @Override + public void visit(EntitySetResponse response) throws ODataTranslatedException, + ODataApplicationException { + selectProcessor(EntityCollectionProcessor.class).readEntityCollection(request.getODataRequest(), + response.getODataResponse(), request.getUriInfo(), request.getResponseContentType()); + } + }); + } + + @Override + public void invoke(final ActionRequest request, String eTag, final T response) + throws ODataTranslatedException, ODataApplicationException { + final HttpMethod method = request.getODataRequest().getMethod(); + if (method != HttpMethod.POST) { + throw new ODataHandlerException("HTTP method " + method + " is not allowed.", + ODataHandlerException.MessageKeys.HTTP_METHOD_NOT_ALLOWED, method.toString()); + } + response.accepts(new ServiceResponseVisior() { + @Override + public void visit(EntityResponse response) throws ODataTranslatedException, + ODataApplicationException { + selectProcessor(EntityProcessor.class).readEntity(request.getODataRequest(), + response.getODataResponse(), request.getUriInfo(), request.getResponseContentType()); + } + + @Override + public void visit(PropertyResponse response) throws ODataTranslatedException, + ODataApplicationException { + if (request.isReturnTypePrimitive()) { + if(request.isCollection()) { + selectProcessor(PrimitiveCollectionProcessor.class).readPrimitiveCollection( + request.getODataRequest(), response.getODataResponse(), request.getUriInfo(), + request.getResponseContentType()); + + } else { + selectProcessor(PrimitiveProcessor.class).readPrimitive( + request.getODataRequest(), response.getODataResponse(), request.getUriInfo(), + request.getResponseContentType()); + } + } else { + if(request.isCollection()) { + selectProcessor(ComplexCollectionProcessor.class).readComplexCollection( + request.getODataRequest(), response.getODataResponse(), request.getUriInfo(), + request.getResponseContentType()); + + } else { + selectProcessor(ComplexProcessor.class).readComplex( + request.getODataRequest(), response.getODataResponse(), request.getUriInfo(), + request.getResponseContentType()); + } + } + } + @Override + public void visit(EntitySetResponse response) throws ODataTranslatedException, + ODataApplicationException { + selectProcessor(EntityCollectionProcessor.class).readEntityCollection(request.getODataRequest(), + response.getODataResponse(), request.getUriInfo(), request.getResponseContentType()); + } + }); + } + + + @Override + public void readMediaStream(MediaRequest request, StreamResponse response) + throws ODataTranslatedException, ODataApplicationException { + selectProcessor(MediaEntityProcessor.class).readMediaEntity( + request.getODataRequest(), response.getODataResponse(), request.getUriInfo(), + request.getResponseContentType()); + } + + @Override + public void upsertMediaStream(MediaRequest request, String entityETag, InputStream mediaContent, + NoContentResponse response) throws ODataTranslatedException, ODataApplicationException { + selectProcessor(MediaEntityProcessor.class).updateMediaEntity( + request.getODataRequest(), response.getODataResponse(), request.getUriInfo(), + request.getRequestContentType(), request.getResponseContentType()); + } + + @Override + public void anyUnsupported(ODataRequest request, ODataResponse response) + throws ODataTranslatedException, ODataApplicationException { + throw new ODataHandlerException("not implemented", + ODataHandlerException.MessageKeys.FUNCTIONALITY_NOT_IMPLEMENTED); + } + + @Override + public void addReference(DataRequest request, String entityETag, List idReferences, + NoContentResponse response) throws ODataTranslatedException, ODataApplicationException { + selectProcessor(ReferenceProcessor.class).createReference( + request.getODataRequest(), response.getODataResponse(), request.getUriInfo(), + request.getResponseContentType()); + } + + @Override + public void updateReference(DataRequest request, String entityETag, URI referenceId, + NoContentResponse response) throws ODataTranslatedException, ODataApplicationException { + selectProcessor(ReferenceProcessor.class).updateReference( + request.getODataRequest(), response.getODataResponse(), request.getUriInfo(), + request.getResponseContentType()); + } + + @Override + public void deleteReference(DataRequest request, URI deleteId, String entityETag, + NoContentResponse response) throws ODataTranslatedException, ODataApplicationException { + selectProcessor(ReferenceProcessor.class).deleteReference( + request.getODataRequest(), response.getODataResponse(), request.getUriInfo()); + } + + @Override + public String startTransaction() { + return null; + } + + @Override + public void commit(String txnId) { + } + + @Override + public void rollback(String txnId) { + } + + @Override + public void crossJoin(DataRequest dataRequest, List entitySetNames, ODataResponse response) + throws ODataTranslatedException, ODataApplicationException { + throw new ODataHandlerException("not implemented", + ODataHandlerException.MessageKeys.FUNCTIONALITY_NOT_IMPLEMENTED); + } +} diff --git a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/requests/ActionRequest.java b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/requests/ActionRequest.java new file mode 100644 index 000000000..133ee3e0c --- /dev/null +++ b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/requests/ActionRequest.java @@ -0,0 +1,120 @@ +/* + * 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.server.core.requests; + +import org.apache.olingo.commons.api.edm.EdmAction; +import org.apache.olingo.commons.api.edm.EdmReturnType; +import org.apache.olingo.server.api.OData; +import org.apache.olingo.server.api.ODataApplicationException; +import org.apache.olingo.server.api.ODataResponse; +import org.apache.olingo.server.api.ODataTranslatedException; +import org.apache.olingo.server.api.ServiceMetadata; +import org.apache.olingo.server.api.uri.UriResourceAction; +import org.apache.olingo.server.core.ServiceHandler; +import org.apache.olingo.server.core.responses.EntityResponse; +import org.apache.olingo.server.core.responses.EntitySetResponse; +import org.apache.olingo.server.core.responses.NoContentResponse; +import org.apache.olingo.server.core.responses.PrimitiveValueResponse; +import org.apache.olingo.server.core.responses.PropertyResponse; + +public class ActionRequest extends OperationRequest { + private UriResourceAction uriResourceAction; + + public ActionRequest(OData odata, ServiceMetadata serviceMetadata) { + super(odata, serviceMetadata); + } + + @Override + public void execute(ServiceHandler handler, ODataResponse response) + throws ODataTranslatedException, ODataApplicationException { + + if (!allowedMethod()) { + methodNotAllowed(); + } + // Actions MAY return data but MUST NOT be further composed with additional + // path segments. + // On success, the response is 201 Created for actions that create entities, + // 200 OK for actions + // that return results or 204 No Content for action without a return type. + // The client can request + // whether any results from the action be returned using the Prefer header. + + if (!hasReturnType()) { + handler.invoke(this, getETag(), new NoContentResponse(getServiceMetaData(), response)); + } else { + if (isReturnTypePrimitive()) { + handler.invoke(this, getETag(), + PrimitiveValueResponse.getInstance(this, response, isCollection(), getReturnType())); + } else if (isReturnTypeComplex()) { + handler.invoke(this, getETag(), PropertyResponse.getInstance(this, response, + getReturnType().getType(), getContextURL(this.odata), isCollection())); + } else { + // EdmTypeKind.ENTITY + if (isCollection()) { + handler.invoke(this, getETag(), + EntitySetResponse.getInstance(this, getContextURL(odata), false, response)); + } else { + handler.invoke(this, getETag(), + EntityResponse.getInstance(this, getContextURL(odata), false, response)); + } + } + } + } + + @Override + public boolean allowedMethod() { + // 11.5.4.1 Invoking an Action - only allows POST + return (isPOST()); + } + + public UriResourceAction getUriResourceAction() { + return uriResourceAction; + } + + public void setUriResourceAction(UriResourceAction uriResourceAction) { + this.uriResourceAction = uriResourceAction; + } + + @Override + public boolean isBound() { + return this.uriResourceAction.getActionImport() != null; + } + + public EdmAction getAction() { + return this.uriResourceAction.getAction(); + } + + @Override + public boolean isCollection() { + assert (hasReturnType()); + return getAction().getReturnType().isCollection(); + } + + @Override + public EdmReturnType getReturnType() { + assert (hasReturnType()); + return getAction().getReturnType(); + } + + @Override + public boolean hasReturnType() { + return getAction().getReturnType() != null; + } +} \ No newline at end of file diff --git a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/requests/BatchRequest.java b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/requests/BatchRequest.java new file mode 100644 index 000000000..25af023e0 --- /dev/null +++ b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/requests/BatchRequest.java @@ -0,0 +1,197 @@ +/* + * 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.server.core.requests; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.UUID; + +import org.apache.olingo.commons.api.format.ContentType; +import org.apache.olingo.commons.api.http.HttpHeader; +import org.apache.olingo.commons.api.http.HttpStatusCode; +import org.apache.olingo.server.api.OData; +import org.apache.olingo.server.api.ODataApplicationException; +import org.apache.olingo.server.api.ODataRequest; +import org.apache.olingo.server.api.ODataResponse; +import org.apache.olingo.server.api.ODataTranslatedException; +import org.apache.olingo.server.api.ServiceMetadata; +import org.apache.olingo.server.api.batch.exception.BatchDeserializerException; +import org.apache.olingo.server.api.deserializer.batch.BatchOptions; +import org.apache.olingo.server.api.deserializer.batch.BatchRequestPart; +import org.apache.olingo.server.api.deserializer.batch.ODataResponsePart; +import org.apache.olingo.server.core.ContentNegotiatorException; +import org.apache.olingo.server.core.ErrorHandler; +import org.apache.olingo.server.core.ServiceDispatcher; +import org.apache.olingo.server.core.ServiceHandler; +import org.apache.olingo.server.core.ServiceRequest; +import org.apache.olingo.server.core.batchhandler.referenceRewriting.BatchReferenceRewriter; +import org.apache.olingo.server.core.deserializer.batch.BatchParserCommon; + +public class BatchRequest extends ServiceRequest { + private static final String PREFERENCE_CONTINUE_ON_ERROR = "odata.continue-on-error"; + private final BatchReferenceRewriter rewriter; + + public BatchRequest(OData odata, ServiceMetadata serviceMetadata) { + super(odata, serviceMetadata); + this.rewriter = new BatchReferenceRewriter(); + } + + @Override + public void execute(ServiceHandler handler, ODataResponse response) + throws ODataTranslatedException, ODataApplicationException { + + if (!allowedMethod()) { + methodNotAllowed(); + } + + validateContentType(); + boolean continueOnError = isContinueOnError(); + final String boundary = extractBoundary(getRequestContentType()); + + final BatchOptions options = BatchOptions.with().rawBaseUri(request.getRawBaseUri()) + .rawServiceResolutionUri(this.request.getRawServiceResolutionUri()).build(); + + final List parts = this.odata.createFixedFormatDeserializer() + .parseBatchRequest(request.getBody(), boundary, options); + + ODataResponsePart partResponse = null; + final List responseParts = new ArrayList(); + + for (BatchRequestPart part : parts) { + if (part.isChangeSet()) { + String txnId = handler.startTransaction(); + partResponse = processChangeSet(part, handler); + if (partResponse.getResponses().get(0).getStatusCode() > 400) { + handler.rollback(txnId); + } + handler.commit(txnId); + } else { + // single request, a static request + ODataRequest partRequest = part.getRequests().get(0); + partResponse = process(partRequest, handler); + } + responseParts.add(partResponse); + + // on error, should we continue? + final int statusCode = partResponse.getResponses().get(0).getStatusCode(); + if ((statusCode >= 400 && statusCode <= 600) && !continueOnError) { + break; + } + } + + // send response + final String responseBoundary = "batch_" + UUID.randomUUID().toString(); + ; + final InputStream responseContent = odata.createFixedFormatSerializer().batchResponse( + responseParts, responseBoundary); + response.setHeader(HttpHeader.CONTENT_TYPE, ContentType.MULTIPART_MIXED + ";boundary=" + + responseBoundary); + response.setContent(responseContent); + response.setStatusCode(HttpStatusCode.ACCEPTED.getStatusCode()); + } + + ODataResponsePart process(ODataRequest partRequest, ServiceHandler serviceHandler) { + ODataResponse partResponse = executeSingleRequest(partRequest, serviceHandler); + addContentID(partRequest, partResponse); + return new ODataResponsePart(partResponse, false); + } + + ODataResponsePart processChangeSet(BatchRequestPart partRequest, ServiceHandler serviceHandler) + throws BatchDeserializerException { + List changeSetResponses = new ArrayList(); + // change set need to be a in a atomic operation + for (ODataRequest changeSetPartRequest : partRequest.getRequests()) { + + this.rewriter.replaceReference(changeSetPartRequest); + + ODataResponse partResponse = executeSingleRequest(changeSetPartRequest, serviceHandler); + + this.rewriter.addMapping(changeSetPartRequest, partResponse); + addContentID(changeSetPartRequest, partResponse); + + if (partResponse.getStatusCode() < 400) { + changeSetResponses.add(partResponse); + } else { + // 11.7.4 Responding to a Batch Request + return new ODataResponsePart(partResponse, false); + } + } + return new ODataResponsePart(changeSetResponses, true); + } + + ODataResponse executeSingleRequest(ODataRequest singleRequest, ServiceHandler handler) { + ServiceDispatcher dispatcher = new ServiceDispatcher(this.odata, this.serviceMetadata, handler, + this.customContentType); + ODataResponse res = new ODataResponse(); + try { + dispatcher.execute(singleRequest, res); + } catch (Exception e) { + ErrorHandler ehandler = new ErrorHandler(this.odata, this.serviceMetadata, + getCustomContentTypeSupport()); + ehandler.handleException(e, singleRequest, res); + } + return res; + } + + private void addContentID(ODataRequest batchPartRequest, ODataResponse batchPartResponse) { + final String contentId = batchPartRequest.getHeader(BatchParserCommon.HTTP_CONTENT_ID); + if (contentId != null) { + batchPartResponse.setHeader(BatchParserCommon.HTTP_CONTENT_ID, contentId); + } + } + + @Override + public boolean allowedMethod() { + return isPOST(); + } + + private void validateContentType() throws ODataApplicationException { + final String contentType = getRequestContentType().toContentTypeString(); + + if (contentType == null + || !BatchParserCommon.PATTERN_MULTIPART_BOUNDARY.matcher(contentType).matches()) { + throw new ODataApplicationException("Invalid content type", + HttpStatusCode.PRECONDITION_FAILED.getStatusCode(), Locale.getDefault()); + } + } + + @Override + public ContentType getResponseContentType() throws ContentNegotiatorException { + return null; + } + + private boolean isContinueOnError() { + final List preferValues = this.request.getHeaders(HttpHeader.PREFER); + + if (preferValues != null) { + for (final String preference : preferValues) { + if (PREFERENCE_CONTINUE_ON_ERROR.equals(preference)) { + return true; + } + } + } + return false; + } + + private String extractBoundary(ContentType contentType) throws BatchDeserializerException { + return BatchParserCommon.getBoundary(contentType.toContentTypeString(), 0); + } +} diff --git a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/requests/DataRequest.java b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/requests/DataRequest.java new file mode 100644 index 000000000..32bf26a57 --- /dev/null +++ b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/requests/DataRequest.java @@ -0,0 +1,769 @@ +/* + * 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.server.core.requests; + +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.LinkedList; +import java.util.List; + +import org.apache.olingo.commons.api.data.ContextURL; +import org.apache.olingo.commons.api.data.ContextURL.Suffix; +import org.apache.olingo.commons.api.data.Entity; +import org.apache.olingo.commons.api.data.Property; +import org.apache.olingo.commons.api.edm.EdmBindingTarget; +import org.apache.olingo.commons.api.edm.EdmComplexType; +import org.apache.olingo.commons.api.edm.EdmEntitySet; +import org.apache.olingo.commons.api.edm.EdmEntityType; +import org.apache.olingo.commons.api.edm.EdmNavigationProperty; +import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind; +import org.apache.olingo.commons.api.edm.EdmProperty; +import org.apache.olingo.commons.api.format.ContentType; +import org.apache.olingo.commons.api.format.ODataFormat; +import org.apache.olingo.commons.api.http.HttpHeader; +import org.apache.olingo.commons.core.data.PropertyImpl; +import org.apache.olingo.commons.core.edm.primitivetype.EdmPrimitiveTypeFactory; +import org.apache.olingo.commons.core.edm.primitivetype.EdmStream; +import org.apache.olingo.server.api.OData; +import org.apache.olingo.server.api.ODataApplicationException; +import org.apache.olingo.server.api.ODataResponse; +import org.apache.olingo.server.api.ODataTranslatedException; +import org.apache.olingo.server.api.ServiceMetadata; +import org.apache.olingo.server.api.deserializer.DeserializerException; +import org.apache.olingo.server.api.deserializer.DeserializerException.MessageKeys; +import org.apache.olingo.server.api.deserializer.ODataDeserializer; +import org.apache.olingo.server.api.serializer.PrimitiveSerializerOptions; +import org.apache.olingo.server.api.serializer.RepresentationType; +import org.apache.olingo.server.api.serializer.SerializerException; +import org.apache.olingo.server.api.uri.UriHelper; +import org.apache.olingo.server.api.uri.UriInfo; +import org.apache.olingo.server.api.uri.UriInfoCrossjoin; +import org.apache.olingo.server.api.uri.UriInfoResource; +import org.apache.olingo.server.api.uri.UriParameter; +import org.apache.olingo.server.api.uri.UriResource; +import org.apache.olingo.server.api.uri.UriResourceComplexProperty; +import org.apache.olingo.server.api.uri.UriResourceEntitySet; +import org.apache.olingo.server.api.uri.UriResourceNavigation; +import org.apache.olingo.server.api.uri.UriResourcePrimitiveProperty; +import org.apache.olingo.server.api.uri.UriResourceProperty; +import org.apache.olingo.server.api.uri.UriResourceSingleton; +import org.apache.olingo.server.core.ContentNegotiator; +import org.apache.olingo.server.core.ContentNegotiatorException; +import org.apache.olingo.server.core.ServiceHandler; +import org.apache.olingo.server.core.ServiceRequest; +import org.apache.olingo.server.core.responses.CountResponse; +import org.apache.olingo.server.core.responses.EntityResponse; +import org.apache.olingo.server.core.responses.EntitySetResponse; +import org.apache.olingo.server.core.responses.NoContentResponse; +import org.apache.olingo.server.core.responses.PrimitiveValueResponse; +import org.apache.olingo.server.core.responses.PropertyResponse; +import org.apache.olingo.server.core.responses.StreamResponse; + +public class DataRequest extends ServiceRequest { + protected UriResourceEntitySet uriResourceEntitySet; + private boolean countRequest; + private UriResourceProperty uriResourceProperty; + private boolean valueRequest; + private final LinkedList uriNavigations = new LinkedList(); + private boolean references; + + private RequestType type; + private UriResourceSingleton uriResourceSingleton; + + /** + * This sub-categorizes the request so that code can be simplified + */ + interface RequestType { + public boolean allowedMethod(); + + public ContentType getResponseContentType() throws ContentNegotiatorException; + + public ContextURL getContextURL(OData odata) throws SerializerException; + + public void execute(ServiceHandler handler, ODataResponse response) + throws ODataTranslatedException, ODataApplicationException; + } + + public DataRequest(OData odata, ServiceMetadata serviceMetadata) { + super(odata, serviceMetadata); + } + + public UriResourceEntitySet getUriResourceEntitySet() { + return uriResourceEntitySet; + } + + public void setUriResourceEntitySet(UriResourceEntitySet uriResourceEntitySet) { + this.uriResourceEntitySet = uriResourceEntitySet; + this.type = new EntityRequest(); + } + + public void setCrossJoin(UriInfoCrossjoin info) { + this.type = new CrossJoinRequest(info.getEntitySetNames()); + } + + public boolean isSingleton() { + return this.uriResourceSingleton != null; + } + + public boolean isCollection() { + if (!this.uriNavigations.isEmpty()) { + return this.uriNavigations.getLast().isCollection(); + } + return this.uriResourceEntitySet != null && this.uriResourceEntitySet.isCollection(); + } + + public EdmEntitySet getEntitySet() { + return this.uriResourceEntitySet.getEntitySet(); + } + + public boolean isCountRequest() { + return countRequest; + } + + public void setCountRequest(boolean countRequest) { + this.countRequest = countRequest; + this.type = new CountRequest(); + } + + public boolean isPropertyRequest() { + return this.uriResourceProperty != null; + } + + public boolean isPropertyComplex() { + return (this.uriResourceProperty instanceof UriResourceComplexProperty); + } + + public boolean isPropertyStream() { + if (isPropertyComplex()) { + return false; + } + EdmProperty property = ((UriResourcePrimitiveProperty)this.uriResourceProperty).getProperty(); + return (property.getType() instanceof EdmStream); + } + + public UriResourceProperty getUriResourceProperty() { + return uriResourceProperty; + } + + public void setUriResourceProperty(UriResourceProperty uriResourceProperty) { + this.uriResourceProperty = uriResourceProperty; + this.type = new PropertyRequest(); + } + + public LinkedList getNavigations() { + return this.uriNavigations; + } + + public void addUriResourceNavigation(UriResourceNavigation uriResourceNavigation) { + this.uriNavigations.add(uriResourceNavigation); + } + + public UriResourceSingleton getUriResourceSingleton() { + return this.uriResourceSingleton; + } + + public void setUriResourceSingleton(UriResourceSingleton info) { + this.uriResourceSingleton = info; + this.type = new SingletonRequest(); + } + + public List getKeyPredicates() { + if (this.uriResourceEntitySet != null) { + return this.uriResourceEntitySet.getKeyPredicates(); + } + return null; + } + + public boolean isReferenceRequest() { + return this.references; + } + + public void setReferenceRequest(boolean ref) { + this.references = ref; + this.type = new ReferenceRequest(); + } + + public boolean isValueRequest() { + return valueRequest; + } + + private boolean hasMediaStream() { + return this.uriResourceEntitySet != null && this.uriResourceEntitySet.getEntityType().hasStream(); + } + + private InputStream getMediaStream() { + return this.request.getBody(); + } + + public void setValueRequest(boolean valueRequest) { + this.valueRequest = valueRequest; + this.type = new ValueRequest(); + } + + @Override + public boolean allowedMethod() { + return this.type.allowedMethod(); + } + + public ContextURL getContextURL(OData odata) throws SerializerException { + return type.getContextURL(odata); + } + + @Override + public void execute(ServiceHandler handler, ODataResponse response) + throws ODataTranslatedException, ODataApplicationException { + + if (!this.type.allowedMethod()) { + methodNotAllowed(); + } + + this.type.execute(handler, response); + } + + @Override + public T getSerializerOptions(Class serilizerOptions, ContextURL contextUrl, boolean references) + throws ContentNegotiatorException { + if (serilizerOptions.isAssignableFrom(PrimitiveSerializerOptions.class)) { + return (T) PrimitiveSerializerOptions.with().contextURL(contextUrl) + .facetsFrom(getUriResourceProperty().getProperty()).build(); + } + return super.getSerializerOptions(serilizerOptions, contextUrl, references); + } + + @Override + public ContentType getResponseContentType() throws ContentNegotiatorException { + return type.getResponseContentType(); + } + + class EntityRequest implements RequestType { + + @Override + public boolean allowedMethod() { + // the create/update/delete to navigation property is done through references + // see # 11.4.6 + if (!getNavigations().isEmpty() && !isGET()) { + return false; + } + return true; + } + + @Override + public ContentType getResponseContentType() throws ContentNegotiatorException { + return ContentNegotiator.doContentNegotiation(uriInfo.getFormatOption(), getODataRequest(), + getCustomContentTypeSupport(), isCollection() ? RepresentationType.COLLECTION_ENTITY + : RepresentationType.ENTITY); + } + + @Override + public void execute(ServiceHandler handler, ODataResponse response) + throws ODataTranslatedException, ODataApplicationException { + + EntityResponse entityResponse = EntityResponse.getInstance(DataRequest.this, + getContextURL(odata), false, response); + + if (isGET()) { + if (isCollection()) { + handler.read(DataRequest.this, + EntitySetResponse.getInstance(DataRequest.this, getContextURL(odata), false, response)); + } else { + handler.read(DataRequest.this,entityResponse); + } + } else if (isPUT() || isPATCH()) { + // RFC 2616: he result of a request having both an If-Match header field and either + // an If-None-Match or an If-Modified-Since header fields is undefined + // by this specification. + boolean ifMatch = getHeader(HttpHeader.IF_MATCH) != null; + boolean ifNoneMatch = getHeader(HttpHeader.IF_NONE_MATCH).equals("*"); + if(ifMatch) { + handler.updateEntity(DataRequest.this, getEntityFromClient(), isPATCH(), getETag(), + entityResponse); + } else if (ifNoneMatch) { + // 11.4.4 + entityResponse = EntityResponse.getInstance(DataRequest.this, + getContextURL(odata), false, response, getReturnRepresentation()); + handler.createEntity(DataRequest.this, getEntityFromClient(), entityResponse); + } else { + handler.updateEntity(DataRequest.this, getEntityFromClient(), isPATCH(), getETag(), + entityResponse); + } + } else if (isPOST()) { + entityResponse = EntityResponse.getInstance(DataRequest.this, + getContextURL(odata), false, response, getReturnRepresentation()); + handler.createEntity(DataRequest.this, getEntityFromClient(),entityResponse); + } else if (isDELETE()) { + handler.deleteEntity(DataRequest.this, getETag(), entityResponse); + } + } + + private Entity getEntityFromClient() throws DeserializerException { + ODataDeserializer deserializer = odata.createDeserializer(ODataFormat + .fromContentType(getRequestContentType())); + return deserializer.entity(getODataRequest().getBody(), getEntitySet().getEntityType()); + } + + @Override + public ContextURL getContextURL(OData odata) throws SerializerException { + // EntitySet based return + final UriHelper helper = odata.createUriHelper(); + ContextURL.Builder builder = buildEntitySetContextURL(helper, getEntitySet(), + getKeyPredicates(), getUriInfo(), getNavigations(), isCollection(), false); + return builder.build(); + } + } + + class CountRequest implements RequestType { + + @Override + public boolean allowedMethod() { + return isGET(); + } + + @Override + public ContentType getResponseContentType() throws ContentNegotiatorException { + return ContentType.TEXT_PLAIN; + } + + @Override + public void execute(ServiceHandler handler, ODataResponse response) + throws ODataTranslatedException, ODataApplicationException { + handler.read(DataRequest.this, CountResponse.getInstance(DataRequest.this, response)); + } + + @Override + public ContextURL getContextURL(OData odata) throws SerializerException { + return null; + } + } + + /** + * Is NavigationProperty Reference. + */ + class ReferenceRequest implements RequestType { + + @Override + public boolean allowedMethod() { + // references are only allowed on the navigation properties + if (getNavigations().isEmpty()) { + return false; + } + + // 11.4.6.1 - post allowed on only collection valued navigation + if (isPOST() && !getNavigations().getLast().isCollection()) { + return false; + } + + // 11.4.6.3 - PUT allowed on single valued navigation + if (isPUT() && getNavigations().getLast().isCollection()) { + return false; + } + + // No defined behavior in spec + if (isPATCH()) { + return false; + } + + return true; + } + + @Override + public ContentType getResponseContentType() throws ContentNegotiatorException { + return ContentNegotiator.doContentNegotiation(uriInfo.getFormatOption(), getODataRequest(), + getCustomContentTypeSupport(), isCollection() ? RepresentationType.COLLECTION_REFERENCE + : RepresentationType.REFERENCE); + } + + @Override + public void execute(ServiceHandler handler, ODataResponse response) + throws ODataTranslatedException, ODataApplicationException { + if (isGET()) { + if (isCollection()) { + handler.read(DataRequest.this, + EntitySetResponse.getInstance(DataRequest.this, getContextURL(odata), true, response)); + } else { + handler.read(DataRequest.this, + EntityResponse.getInstance(DataRequest.this, getContextURL(odata), true, response)); + } + } else if (isDELETE()) { + // if this against the collection, user need to look at $id param for entity ref #11.4.6.2 + String id = getQueryParameter("$id"); + if (id == null) { + handler.deleteReference(DataRequest.this, null, getETag(), new NoContentResponse( + getServiceMetaData(), response)); + } else { + try { + handler.deleteReference(DataRequest.this, new URI(id), getETag(), new NoContentResponse( + getServiceMetaData(), response)); + } catch (URISyntaxException e) { + throw new DeserializerException("failed to read $id", e, MessageKeys.UNKOWN_CONTENT); + } + } + } else if (isPUT()) { + // note this is always against single reference + handler.updateReference(DataRequest.this, getETag(), getPayload().get(0), new NoContentResponse( + getServiceMetaData(), response)); + } else if (isPOST()) { + // this needs to be against collection of references + handler.addReference(DataRequest.this, getETag(), getPayload(), new NoContentResponse( + getServiceMetaData(), response)); + } + } + + // http://docs.oasis-open.org/odata/odata-json-format/v4.0/errata02/os + // /odata-json-format-v4.0-errata02-os-complete.html#_Toc403940643 + // The below code reads as property and converts to an URI + private List getPayload() throws DeserializerException { + ODataDeserializer deserializer = odata.createDeserializer(ODataFormat + .fromContentType(getRequestContentType())); + return deserializer.entityReferences(getODataRequest().getBody()); + } + + @Override + public ContextURL getContextURL(OData odata) throws SerializerException { + ContextURL.Builder builder = ContextURL.with().suffix(Suffix.REFERENCE); + if (isCollection()) { + builder.asCollection(); + } + return builder.build(); + } + } + + class PropertyRequest implements RequestType { + + @Override + public boolean allowedMethod() { + // create of properties is not allowed, + // only read, update, delete. Note that delete is + // same as update with null + if (isPOST()) { + return false; + } + + // 11.4.9.4, collection properties are not supported with merge + if (isPATCH() && (isCollection() || isPropertyStream())) { + return false; + } + return true; + } + + @Override + public ContentType getResponseContentType() throws ContentNegotiatorException { + if (isPropertyComplex()) { + return ContentNegotiator.doContentNegotiation(getUriInfo().getFormatOption(), + getODataRequest(), getCustomContentTypeSupport(), + isCollection() ? RepresentationType.COLLECTION_COMPLEX : RepresentationType.COMPLEX); + } else if (isPropertyStream()) { + return ContentNegotiator.doContentNegotiation(uriInfo.getFormatOption(), request, + getCustomContentTypeSupport(), RepresentationType.BINARY); + } + return ContentNegotiator.doContentNegotiation(uriInfo.getFormatOption(), getODataRequest(), + getCustomContentTypeSupport(), isCollection() ? RepresentationType.COLLECTION_PRIMITIVE + : RepresentationType.PRIMITIVE); + } + + @Override + public void execute(ServiceHandler handler, ODataResponse response) + throws ODataTranslatedException, ODataApplicationException { + + EdmProperty edmProperty = getUriResourceProperty().getProperty(); + + if (isGET()) { + if (isPropertyStream()) { + handler.read(DataRequest.this, new StreamResponse(getServiceMetaData(), response)); + } else { + handler.read(DataRequest.this, buildResponse(response, edmProperty)); + } + } else if (isPATCH()) { + handler.updateProperty(DataRequest.this, getPropertyValueFromClient(edmProperty), true, + getETag(), buildResponse(response, edmProperty)); + } else if (isPUT()) { + if (isPropertyStream()) { + handler.upsertStreamProperty(DataRequest.this, getETag(), request.getBody(), + new NoContentResponse(getServiceMetaData(), response)); + } else { + handler.updateProperty(DataRequest.this, getPropertyValueFromClient(edmProperty), false, + getETag(), buildResponse(response, edmProperty)); + } + } else if (isDELETE()) { + if (isPropertyStream()) { + handler.upsertStreamProperty(DataRequest.this, getETag(), request.getBody(), + new NoContentResponse(getServiceMetaData(), response)); + } else { + Property property = new PropertyImpl(); + property.setName(edmProperty.getName()); + property.setType(edmProperty.getType().getFullQualifiedName() + .getFullQualifiedNameAsString()); + handler.updateProperty(DataRequest.this, property, false, getETag(), + buildResponse(response, edmProperty)); + } + } + } + + private PropertyResponse buildResponse(ODataResponse response, EdmProperty edmProperty) + throws ContentNegotiatorException, SerializerException { + PropertyResponse propertyResponse = PropertyResponse.getInstance(DataRequest.this, response, + edmProperty.getType(), getContextURL(odata), edmProperty.isCollection()); + return propertyResponse; + } + + @Override + public ContextURL getContextURL(OData odata) throws SerializerException { + final UriHelper helper = odata.createUriHelper(); + EdmProperty edmProperty = getUriResourceProperty().getProperty(); + + ContextURL.Builder builder = ContextURL.with().entitySet(getEntitySet()); + builder = ContextURL.with().entitySet(getEntitySet()); + builder.keyPath(helper.buildContextURLKeyPredicate(getUriResourceEntitySet() + .getKeyPredicates())); + String navPath = buildNavPath(helper, getEntitySet().getEntityType(), getNavigations(), true); + if (navPath != null && !navPath.isEmpty()) { + builder.navOrPropertyPath(navPath+"/"+edmProperty.getName()); + } else { + builder.navOrPropertyPath(edmProperty.getName()); + } + if (isPropertyComplex()) { + EdmComplexType type = ((UriResourceComplexProperty) uriResourceProperty).getComplexType(); + String select = helper.buildContextURLSelectList(type, getUriInfo().getExpandOption(), + getUriInfo().getSelectOption()); + builder.selectList(select); + } + return builder.build(); + } + } + + class ValueRequest extends PropertyRequest { + + @Override + public boolean allowedMethod() { + //part2-url-conventions # 4.2 + if (isPropertyStream() && isGET()) { + return false; + } + + return isGET() || isDELETE() || isPUT(); + } + + @Override + public ContentType getResponseContentType() throws ContentNegotiatorException { + RepresentationType valueRepresentationType = uriResourceProperty.getType() == EdmPrimitiveTypeFactory + .getInstance(EdmPrimitiveTypeKind.Binary) ? RepresentationType.BINARY + : RepresentationType.VALUE; + return ContentNegotiator.doContentNegotiation(uriInfo.getFormatOption(), request, + getCustomContentTypeSupport(), valueRepresentationType); + } + + @Override + public void execute(ServiceHandler handler, ODataResponse response) + throws ODataTranslatedException, ODataApplicationException { + EdmProperty edmProperty = getUriResourceProperty().getProperty(); + if (isGET()) { + handler.read(DataRequest.this, PrimitiveValueResponse.getInstance(DataRequest.this, + response, isCollection(), getUriResourceProperty().getProperty())); + } else if (isDELETE()) { + Property property = new PropertyImpl(); + property.setName(edmProperty.getName()); + property.setType(edmProperty.getType().getFullQualifiedName().getFullQualifiedNameAsString()); + + PropertyResponse propertyResponse = PropertyResponse.getInstance(DataRequest.this, response, + edmProperty.getType(), getContextURL(odata), edmProperty.isCollection()); + handler.updateProperty(DataRequest.this, property, false, getETag(), propertyResponse); + } else if (isPUT()) { + PropertyResponse propertyResponse = PropertyResponse.getInstance(DataRequest.this, response, + edmProperty.getType(), getContextURL(odata), edmProperty.isCollection()); + handler.updateProperty(DataRequest.this, getPropertyValueFromClient(edmProperty), false, + getETag(), propertyResponse); + } + } + + @Override + public ContextURL getContextURL(OData odata) throws SerializerException { + return null; + } + } + + class SingletonRequest implements RequestType { + + @Override + public boolean allowedMethod() { + return isGET(); + } + + @Override + public ContentType getResponseContentType() throws ContentNegotiatorException { + return ContentNegotiator.doContentNegotiation(uriInfo.getFormatOption(), getODataRequest(), + getCustomContentTypeSupport(), RepresentationType.ENTITY); + } + + @Override + public ContextURL getContextURL(OData odata) throws SerializerException { + final UriHelper helper = odata.createUriHelper(); + ContextURL.Builder builder = buildEntitySetContextURL(helper, + uriResourceSingleton.getSingleton(), null, getUriInfo(), getNavigations(), isCollection(), true); + return builder.build(); + } + + @Override + public void execute(ServiceHandler handler, ODataResponse response) + throws ODataTranslatedException, ODataApplicationException { + handler.read(DataRequest.this, + EntityResponse.getInstance(DataRequest.this, getContextURL(odata), false, response)); + } + } + + class CrossJoinRequest implements RequestType { + private final List entitySetNames; + + public CrossJoinRequest(List entitySetNames) { + this.entitySetNames = entitySetNames; + } + + @Override + public boolean allowedMethod() { + return isGET(); + } + + @Override + public ContentType getResponseContentType() throws ContentNegotiatorException { + return ContentNegotiator.doContentNegotiation(getUriInfo().getFormatOption(), + getODataRequest(), getCustomContentTypeSupport(), RepresentationType.COLLECTION_COMPLEX); + } + + @Override + public void execute(ServiceHandler handler, ODataResponse response) + throws ODataTranslatedException, ODataApplicationException { + handler.crossJoin(DataRequest.this, this.entitySetNames, response); + } + + @Override + public ContextURL getContextURL(OData odata) throws SerializerException { + ContextURL.Builder builder = ContextURL.with().asCollection(); + return builder.build(); + } + } + + private org.apache.olingo.commons.api.data.Property getPropertyValueFromClient( + EdmProperty edmProperty) throws DeserializerException { + // TODO:this is not right, we should be deserializing the property + // (primitive, complex, collection of) + // for now it is responsibility of the user + ODataDeserializer deserializer = odata.createDeserializer(ODataFormat + .fromContentType(getRequestContentType())); + return deserializer.property(getODataRequest().getBody(), edmProperty); + } + + static ContextURL.Builder buildEntitySetContextURL(UriHelper helper, + EdmBindingTarget edmEntitySet, List keyPredicates, UriInfo uriInfo, + LinkedList navigations, boolean collectionReturn, boolean singleton) + throws SerializerException { + + ContextURL.Builder builder = ContextURL.with().entitySetOrSingletonOrType(edmEntitySet.getName()); + String select = helper.buildContextURLSelectList(edmEntitySet.getEntityType(), + uriInfo.getExpandOption(), uriInfo.getSelectOption()); + if (!singleton) { + builder.suffix(collectionReturn ? null : Suffix.ENTITY); + } + + builder.selectList(select); + + final UriInfoResource resource = uriInfo.asUriInfoResource(); + final List resourceParts = resource.getUriResourceParts(); + final List path = getPropertyPath(resourceParts); + String propertyPath = buildPropertyPath(path); + final String navPath = buildNavPath(helper, edmEntitySet.getEntityType(), navigations, collectionReturn); + if (navPath != null && !navPath.isEmpty()) { + if (keyPredicates != null) { + builder.keyPath(helper.buildContextURLKeyPredicate(keyPredicates)); + } + if (propertyPath != null) { + propertyPath = navPath+"/"+propertyPath; + } else { + propertyPath = navPath; + } + } + builder.navOrPropertyPath(propertyPath); + return builder; + } + + private static List getPropertyPath(final List path) { + List result = new LinkedList(); + int index = 1; + while (index < path.size() && path.get(index) instanceof UriResourceProperty) { + result.add(((UriResourceProperty) path.get(index)).getProperty().getName()); + index++; + } + return result; + } + + private static String buildPropertyPath(final List path) { + StringBuilder result = new StringBuilder(); + for (final String segment : path) { + result.append(result.length() == 0 ? "" : '/').append(segment); //$NON-NLS-1$ + } + return result.length() == 0?null:result.toString(); + } + + static String buildNavPath(UriHelper helper, EdmEntityType rootType, + LinkedList navigations, boolean includeLastPredicates) + throws SerializerException { + if (navigations.isEmpty()) { + return null; + } + StringBuilder sb = new StringBuilder(); + boolean containsTarget = false; + EdmEntityType type = rootType; + for (UriResourceNavigation nav:navigations) { + String name = nav.getProperty().getName(); + EdmNavigationProperty property = type.getNavigationProperty(name); + if (property.containsTarget()) { + containsTarget = true; + } + type = nav.getProperty().getType(); + } + + if (containsTarget) { + for (int i = 0; i < navigations.size(); i++) { + UriResourceNavigation nav = navigations.get(i); + if (i > 0) { + sb.append("/"); + } + sb.append(nav.getProperty().getName()); + + boolean skipKeys = false; + if (navigations.size() == i+1 && !includeLastPredicates ) { + skipKeys = true; + } + + if (!skipKeys && !nav.getKeyPredicates().isEmpty()) { + sb.append("("); + sb.append(helper.buildContextURLKeyPredicate(nav.getKeyPredicates())); + sb.append(")"); + } + + if (nav.getTypeFilterOnCollection() != null) { + sb.append("/") + .append(nav.getTypeFilterOnCollection().getFullQualifiedName().getFullQualifiedNameAsString()); + } else if (nav.getTypeFilterOnEntry() != null) { + sb.append("/") + .append(nav.getTypeFilterOnEntry().getFullQualifiedName().getFullQualifiedNameAsString()); + } + } + } + return sb.toString(); + } +} diff --git a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/requests/FunctionRequest.java b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/requests/FunctionRequest.java new file mode 100644 index 000000000..a9f9341d6 --- /dev/null +++ b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/requests/FunctionRequest.java @@ -0,0 +1,122 @@ +/* + * 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.server.core.requests; + +import java.util.List; + +import org.apache.olingo.commons.api.edm.EdmFunction; +import org.apache.olingo.commons.api.edm.EdmReturnType; +import org.apache.olingo.server.api.OData; +import org.apache.olingo.server.api.ODataApplicationException; +import org.apache.olingo.server.api.ODataResponse; +import org.apache.olingo.server.api.ODataTranslatedException; +import org.apache.olingo.server.api.ServiceMetadata; +import org.apache.olingo.server.api.uri.UriParameter; +import org.apache.olingo.server.api.uri.UriResourceFunction; +import org.apache.olingo.server.core.ServiceHandler; +import org.apache.olingo.server.core.responses.EntityResponse; +import org.apache.olingo.server.core.responses.EntitySetResponse; +import org.apache.olingo.server.core.responses.PrimitiveValueResponse; +import org.apache.olingo.server.core.responses.PropertyResponse; + +public class FunctionRequest extends OperationRequest { + private UriResourceFunction uriResourceFunction; + + public FunctionRequest(OData odata, ServiceMetadata serviceMetadata) { + super(odata, serviceMetadata); + } + + @Override + public void execute(ServiceHandler handler, ODataResponse response) + throws ODataTranslatedException, ODataApplicationException { + + if (!allowedMethod()) { + methodNotAllowed(); + } + + // Functions always have return per 11.5.3 + if (isReturnTypePrimitive()) { + // functions can not return a typed property in the context of entity, so + // it must be treated + // as value based response + handler.invoke(this, getODataRequest().getMethod(), + PrimitiveValueResponse.getInstance(this, response, isCollection(), getReturnType())); + } else if (isReturnTypeComplex()) { + handler.invoke(this, getODataRequest().getMethod(), PropertyResponse.getInstance(this, response, + getReturnType().getType(), getContextURL(this.odata), isCollection())); + } else { + // returnType.getType().getKind() == EdmTypeKind.ENTITY + if (isCollection()) { + handler.invoke(this, getODataRequest().getMethod(), + EntitySetResponse.getInstance(this, getContextURL(odata), false, response)); + } else { + handler.invoke(this, getODataRequest().getMethod(), + EntityResponse.getInstance(this, getContextURL(odata), false, response)); + } + } + } + + @Override + public boolean allowedMethod() { + // look for discussion about composable functions in odata-discussion + // group with thread "Clarification on "Function" invocations" + if (getFunction().isComposable()) { + return (isGET() || isPATCH() || isDELETE() || isPOST() || isPUT()); + } + return isGET(); + } + + public UriResourceFunction getUriResourceFunction() { + return uriResourceFunction; + } + + public void setUriResourceFunction(UriResourceFunction uriResourceFunction) { + this.uriResourceFunction = uriResourceFunction; + } + + @Override + public boolean isBound() { + return this.uriResourceFunction.getFunctionImport() != null; + } + + public EdmFunction getFunction() { + return this.uriResourceFunction.getFunction(); + } + + public List getParameters() { + return this.uriResourceFunction.getParameters(); + } + + @Override + public boolean isCollection() { + return getFunction().getReturnType().isCollection(); + } + + @Override + public EdmReturnType getReturnType() { + return getFunction().getReturnType(); + } + + @Override + public boolean hasReturnType() { + // Part3 {12.1} says must have return type + return true; + } +} \ No newline at end of file diff --git a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/requests/MediaRequest.java b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/requests/MediaRequest.java new file mode 100644 index 000000000..a4a333a1d --- /dev/null +++ b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/requests/MediaRequest.java @@ -0,0 +1,99 @@ +/* + * 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.server.core.requests; + +import java.io.InputStream; +import java.util.List; + +import org.apache.olingo.commons.api.edm.EdmEntitySet; +import org.apache.olingo.commons.api.edm.EdmEntityType; +import org.apache.olingo.commons.api.format.ContentType; +import org.apache.olingo.server.api.OData; +import org.apache.olingo.server.api.ODataApplicationException; +import org.apache.olingo.server.api.ODataResponse; +import org.apache.olingo.server.api.ODataTranslatedException; +import org.apache.olingo.server.api.ServiceMetadata; +import org.apache.olingo.server.api.uri.UriParameter; +import org.apache.olingo.server.api.uri.UriResourceEntitySet; +import org.apache.olingo.server.core.ContentNegotiatorException; +import org.apache.olingo.server.core.ServiceHandler; +import org.apache.olingo.server.core.ServiceRequest; +import org.apache.olingo.server.core.responses.NoContentResponse; +import org.apache.olingo.server.core.responses.StreamResponse; + +public class MediaRequest extends ServiceRequest { + private UriResourceEntitySet uriResourceEntitySet; + + public MediaRequest(OData odata, ServiceMetadata serviceMetadata) { + super(odata, serviceMetadata); + } + + @Override + public void execute(ServiceHandler handler, ODataResponse response) + throws ODataTranslatedException, ODataApplicationException { + if (!allowedMethod()) { + methodNotAllowed(); + } + // POST will not be here, because the media is created as part of media + // entity creation + if (isGET()) { + handler.readMediaStream(this, new StreamResponse(getServiceMetaData(), response)); + } else if (isPUT()) { + handler.upsertMediaStream(this, getETag(), getMediaStream(), new NoContentResponse( + getServiceMetaData(), response)); + } else if (isDELETE()) { + handler.upsertMediaStream(this, getETag(), null, new NoContentResponse(getServiceMetaData(), + response)); + } + } + + @Override + public ContentType getResponseContentType() throws ContentNegotiatorException { + // the request must specify the content type requested. + return getRequestContentType(); + } + + public EdmEntitySet getEntitySet() { + return this.uriResourceEntitySet.getEntitySet(); + } + + public EdmEntityType getEntityType() { + return this.uriResourceEntitySet.getEntitySet().getEntityType(); + } + + public void setUriResourceEntitySet(UriResourceEntitySet uriResourceEntitySet) { + this.uriResourceEntitySet = uriResourceEntitySet; + } + + public List getKeyPredicates() { + if (this.uriResourceEntitySet != null) { + return this.uriResourceEntitySet.getKeyPredicates(); + } + return null; + } + + private InputStream getMediaStream() { + return this.request.getBody(); + } + + @Override + public boolean allowedMethod() { + return isGET() || isPUT() || isDELETE(); + } +} diff --git a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/requests/MetadataRequest.java b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/requests/MetadataRequest.java new file mode 100644 index 000000000..e2c5c5476 --- /dev/null +++ b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/requests/MetadataRequest.java @@ -0,0 +1,61 @@ +/* + * 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.server.core.requests; + +import org.apache.olingo.commons.api.format.ContentType; +import org.apache.olingo.server.api.OData; +import org.apache.olingo.server.api.ODataApplicationException; +import org.apache.olingo.server.api.ODataResponse; +import org.apache.olingo.server.api.ODataTranslatedException; +import org.apache.olingo.server.api.ServiceMetadata; +import org.apache.olingo.server.api.serializer.RepresentationType; +import org.apache.olingo.server.api.uri.UriInfoMetadata; +import org.apache.olingo.server.core.ContentNegotiator; +import org.apache.olingo.server.core.ContentNegotiatorException; +import org.apache.olingo.server.core.ServiceHandler; +import org.apache.olingo.server.core.ServiceRequest; +import org.apache.olingo.server.core.responses.MetadataResponse; + +public class MetadataRequest extends ServiceRequest { + + public MetadataRequest(OData odata, ServiceMetadata serviceMetadata) { + super(odata, serviceMetadata); + } + + @Override + public ContentType getResponseContentType() throws ContentNegotiatorException { + return ContentNegotiator.doContentNegotiation(this.uriInfo.getFormatOption(), this.request, + this.customContentType, RepresentationType.METADATA); + } + + public UriInfoMetadata getUriInfoMetadata() { + return uriInfo.asUriInfoMetadata(); + } + + @Override + public void execute(ServiceHandler handler, ODataResponse response) + throws ODataTranslatedException, ODataApplicationException { + + if (!allowedMethod()) { + methodNotAllowed(); + } + + handler.readMetadata(this, MetadataResponse.getInstance(this, response)); + } +} diff --git a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/requests/OperationRequest.java b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/requests/OperationRequest.java new file mode 100644 index 000000000..1f1b1946b --- /dev/null +++ b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/requests/OperationRequest.java @@ -0,0 +1,118 @@ +/* + * 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.server.core.requests; + +import org.apache.olingo.commons.api.data.ContextURL; +import org.apache.olingo.commons.api.edm.EdmReturnType; +import org.apache.olingo.commons.api.edm.constants.EdmTypeKind; +import org.apache.olingo.commons.api.format.ContentType; +import org.apache.olingo.server.api.OData; +import org.apache.olingo.server.api.ServiceMetadata; +import org.apache.olingo.server.api.serializer.RepresentationType; +import org.apache.olingo.server.api.serializer.SerializerException; +import org.apache.olingo.server.api.uri.UriHelper; +import org.apache.olingo.server.core.ContentNegotiator; +import org.apache.olingo.server.core.ContentNegotiatorException; +import org.apache.olingo.server.core.ServiceRequest; + +public abstract class OperationRequest extends ServiceRequest { + + public OperationRequest(OData odata, ServiceMetadata serviceMetadata) { + super(odata, serviceMetadata); + } + + @Override + public ContentType getResponseContentType() throws ContentNegotiatorException { + if (!hasReturnType()) { + // this default content type + return ContentType.APPLICATION_OCTET_STREAM; + } + + if (isReturnTypePrimitive()) { + return ContentNegotiator.doContentNegotiation(uriInfo.getFormatOption(), this.request, + getCustomContentTypeSupport(), isCollection() ? RepresentationType.COLLECTION_PRIMITIVE + : RepresentationType.PRIMITIVE); + } else if (isReturnTypeComplex()) { + return ContentNegotiator.doContentNegotiation(uriInfo.getFormatOption(), this.request, + getCustomContentTypeSupport(), isCollection() ? RepresentationType.COLLECTION_COMPLEX + : RepresentationType.COMPLEX); + } else { + return ContentNegotiator.doContentNegotiation(uriInfo.getFormatOption(), this.request, + getCustomContentTypeSupport(), isCollection() ? RepresentationType.COLLECTION_ENTITY + : RepresentationType.ENTITY); + } + } + + public abstract boolean isBound(); + + public abstract boolean isCollection(); + + public abstract EdmReturnType getReturnType(); + + public abstract boolean hasReturnType(); + + public ContextURL getContextURL(OData odata) throws SerializerException { + if (!hasReturnType()) { + return null; + } + + final UriHelper helper = odata.createUriHelper(); + + if (isReturnTypePrimitive() || isReturnTypeComplex()) { + // Part 1 {10.14, 10.14} since the function return properties does not + // represent a Entity property + ContextURL.Builder builder = ContextURL.with().type(getReturnType().getType()); + if (isCollection()) { + builder.asCollection(); + } + return builder.build(); + } + + /* + // EdmTypeKind.ENTITY; + if (isBound()) { + // Bound means, we know the EnitySet of the return type. Part 1 {10.2, + // 10.3} + EdmEntitySet entitySet = this.uriResourceFunction.getFunctionImport().getReturnedEntitySet(); + ContextURL.Builder builder = DataRequest.buildEntitySetContextURL(helper, entitySet, + this.uriInfo, isCollection(), false); + return builder.build(); + } + */ + + // EdmTypeKind.ENTITY; Not Bound + // Here we do not know the EntitySet, then follow directions from + // Part-1{10.2. 10.3} to use + // {context-url}#{type-name} + ContextURL.Builder builder = ContextURL.with().type(getReturnType().getType()); + if (isCollection()) { + builder.asCollection(); + } + return builder.build(); + } + + public boolean isReturnTypePrimitive() { + return getReturnType().getType().getKind() == EdmTypeKind.PRIMITIVE; + } + + public boolean isReturnTypeComplex() { + return getReturnType().getType().getKind() == EdmTypeKind.COMPLEX; + } +} \ No newline at end of file diff --git a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/requests/ServiceDocumentRequest.java b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/requests/ServiceDocumentRequest.java new file mode 100644 index 000000000..f99aaf56e --- /dev/null +++ b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/requests/ServiceDocumentRequest.java @@ -0,0 +1,57 @@ +/* + * 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.server.core.requests; + +import org.apache.olingo.commons.api.format.ContentType; +import org.apache.olingo.server.api.OData; +import org.apache.olingo.server.api.ODataApplicationException; +import org.apache.olingo.server.api.ODataResponse; +import org.apache.olingo.server.api.ODataTranslatedException; +import org.apache.olingo.server.api.ServiceMetadata; +import org.apache.olingo.server.api.serializer.RepresentationType; +import org.apache.olingo.server.core.ContentNegotiator; +import org.apache.olingo.server.core.ContentNegotiatorException; +import org.apache.olingo.server.core.ServiceHandler; +import org.apache.olingo.server.core.ServiceRequest; +import org.apache.olingo.server.core.responses.ServiceDocumentResponse; + +public class ServiceDocumentRequest extends ServiceRequest { + + public ServiceDocumentRequest(OData odata, ServiceMetadata serviceMetadata) { + super(odata, serviceMetadata); + } + + @Override + public ContentType getResponseContentType() throws ContentNegotiatorException { + return ContentNegotiator.doContentNegotiation(getUriInfo().getFormatOption(), + getODataRequest(), getCustomContentTypeSupport(), RepresentationType.SERVICE); + } + + @Override + public void execute(ServiceHandler handler, ODataResponse response) + throws ODataTranslatedException, ODataApplicationException { + + if (!allowedMethod()) { + methodNotAllowed(); + } + handler.readServiceDocument(this, + ServiceDocumentResponse.getInstace(this, response, getResponseContentType())); + } +} diff --git a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/responses/CountResponse.java b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/responses/CountResponse.java new file mode 100644 index 000000000..f7cde332b --- /dev/null +++ b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/responses/CountResponse.java @@ -0,0 +1,60 @@ +/* + * 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.server.core.responses; + +import java.util.Map; + +import org.apache.olingo.commons.api.http.HttpContentType; +import org.apache.olingo.server.api.ODataApplicationException; +import org.apache.olingo.server.api.ODataResponse; +import org.apache.olingo.server.api.ODataTranslatedException; +import org.apache.olingo.server.api.ServiceMetadata; +import org.apache.olingo.server.api.serializer.FixedFormatSerializer; +import org.apache.olingo.server.api.serializer.SerializerException; +import org.apache.olingo.server.core.ServiceRequest; + +public class CountResponse extends ServiceResponse { + private final FixedFormatSerializer serializer; + + public static CountResponse getInstance(ServiceRequest request, ODataResponse response) { + FixedFormatSerializer serializer = request.getOdata().createFixedFormatSerializer(); + return new CountResponse(request.getServiceMetaData(), serializer, response, + request.getPreferences()); + } + + private CountResponse(ServiceMetadata metadata, FixedFormatSerializer serializer, + ODataResponse response, Map preferences) { + super(metadata, response, preferences); + this.serializer = serializer; + } + + public void writeCount(int count) throws SerializerException { + assert (!isClosed()); + + this.response.setContent(this.serializer.count(count)); + writeOK(HttpContentType.TEXT_PLAIN); + close(); + } + + @Override + public void accepts(ServiceResponseVisior visitor) throws ODataTranslatedException, + ODataApplicationException { + visitor.visit(this); + } +} diff --git a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/responses/EntityResponse.java b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/responses/EntityResponse.java new file mode 100644 index 000000000..fd29bbd03 --- /dev/null +++ b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/responses/EntityResponse.java @@ -0,0 +1,140 @@ +/* + * 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.server.core.responses; + +import java.util.Map; + +import org.apache.olingo.commons.api.data.ContextURL; +import org.apache.olingo.commons.api.data.Entity; +import org.apache.olingo.commons.api.edm.EdmEntityType; +import org.apache.olingo.commons.api.format.ContentType; +import org.apache.olingo.commons.api.http.HttpHeader; +import org.apache.olingo.commons.api.http.HttpStatusCode; +import org.apache.olingo.server.api.ODataApplicationException; +import org.apache.olingo.server.api.ODataResponse; +import org.apache.olingo.server.api.ODataTranslatedException; +import org.apache.olingo.server.api.ServiceMetadata; +import org.apache.olingo.server.api.serializer.EntitySerializerOptions; +import org.apache.olingo.server.api.serializer.ODataSerializer; +import org.apache.olingo.server.api.serializer.SerializerException; +import org.apache.olingo.server.core.ContentNegotiatorException; +import org.apache.olingo.server.core.ReturnRepresentation; +import org.apache.olingo.server.core.ServiceRequest; + +public class EntityResponse extends ServiceResponse { + private final ReturnRepresentation returnRepresentation; + private final ODataSerializer serializer; + private final EntitySerializerOptions options; + private final ContentType responseContentType; + + private EntityResponse(ServiceMetadata metadata, ODataResponse response, + ODataSerializer serializer, EntitySerializerOptions options, ContentType responseContentType, + Map preferences, ReturnRepresentation returnRepresentation) { + super(metadata, response, preferences); + this.serializer = serializer; + this.options = options; + this.responseContentType = responseContentType; + this.returnRepresentation = returnRepresentation; + } + + public static EntityResponse getInstance(ServiceRequest request, ContextURL contextURL, + boolean references, ODataResponse response, ReturnRepresentation returnRepresentation) + throws ContentNegotiatorException, SerializerException { + EntitySerializerOptions options = request.getSerializerOptions(EntitySerializerOptions.class, + contextURL, references); + return new EntityResponse(request.getServiceMetaData(), response, request.getSerializer(), + options, request.getResponseContentType(), request.getPreferences(), returnRepresentation); + } + + public static EntityResponse getInstance(ServiceRequest request, ContextURL contextURL, + boolean references, ODataResponse response) + throws ContentNegotiatorException, SerializerException { + EntitySerializerOptions options = request.getSerializerOptions(EntitySerializerOptions.class, + contextURL, references); + return new EntityResponse(request.getServiceMetaData(), response, request.getSerializer(), + options, request.getResponseContentType(), request.getPreferences(), null); + } + + // write single entity + public void writeReadEntity(EdmEntityType entityType, Entity entity) throws SerializerException { + + assert (!isClosed()); + + if (entity == null) { + writeNotFound(true); + return; + } + + // write the entity to response + this.response.setContent(this.serializer.entity(this.metadata, entityType, entity, this.options)); + writeOK(this.responseContentType.toContentTypeString()); + close(); + } + + public void writeCreatedEntity(EdmEntityType entityType, Entity entity, String locationHeader) + throws SerializerException { + // upsert/insert must created a entity, otherwise should have throw an + // exception + assert (entity != null); + + // Note that if media written just like Stream, but on entity URL + + // 8.2.8.7 + if (this.returnRepresentation == ReturnRepresentation.MINIMAL) { + writeNoContent(false); + writeHeader(HttpHeader.LOCATION, locationHeader); + writeHeader("Preference-Applied", "return=minimal"); //$NON-NLS-1$ //$NON-NLS-2$ + // 8.3.3 + writeHeader("OData-EntityId", entity.getId().toASCIIString()); //$NON-NLS-1$ + close(); + return; + } + + // return the content of the created entity + this.response.setContent(this.serializer.entity(this.metadata, entityType, entity, this.options)); + writeCreated(false); + writeHeader(HttpHeader.LOCATION, locationHeader); + writeHeader("Preference-Applied", "return=representation"); //$NON-NLS-1$ //$NON-NLS-2$ + writeHeader(HttpHeader.CONTENT_TYPE, this.responseContentType.toContentTypeString()); + close(); + } + + public void writeUpdatedEntity() { + // spec says just success response; so either 200 or 204. 200 typically has + // payload + writeNoContent(true); + } + + public void writeDeletedEntityOrReference() { + writeNoContent(true); + } + + @Override + public void accepts(ServiceResponseVisior visitor) throws ODataTranslatedException, + ODataApplicationException { + visitor.visit(this); + } + + public void writeCreated(boolean closeResponse) { + this.response.setStatusCode(HttpStatusCode.CREATED.getStatusCode()); + if (closeResponse) { + close(); + } + } +} diff --git a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/responses/EntitySetResponse.java b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/responses/EntitySetResponse.java new file mode 100644 index 000000000..40276d210 --- /dev/null +++ b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/responses/EntitySetResponse.java @@ -0,0 +1,82 @@ +/* + * 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.server.core.responses; + +import java.util.Map; + +import org.apache.olingo.commons.api.data.ContextURL; +import org.apache.olingo.commons.api.data.EntitySet; +import org.apache.olingo.commons.api.edm.EdmEntityType; +import org.apache.olingo.commons.api.format.ContentType; +import org.apache.olingo.server.api.ODataApplicationException; +import org.apache.olingo.server.api.ODataResponse; +import org.apache.olingo.server.api.ODataTranslatedException; +import org.apache.olingo.server.api.ServiceMetadata; +import org.apache.olingo.server.api.serializer.EntityCollectionSerializerOptions; +import org.apache.olingo.server.api.serializer.ODataSerializer; +import org.apache.olingo.server.api.serializer.SerializerException; +import org.apache.olingo.server.core.ContentNegotiatorException; +import org.apache.olingo.server.core.ServiceRequest; + +public class EntitySetResponse extends ServiceResponse { + private final ODataSerializer serializer; + private final EntityCollectionSerializerOptions options; + private final ContentType responseContentType; + + private EntitySetResponse(ServiceMetadata metadata, ODataResponse response, ODataSerializer serializer, + EntityCollectionSerializerOptions options, + ContentType responseContentType, Map preferences) { + super(metadata, response, preferences); + this.serializer = serializer; + this.options = options; + this.responseContentType = responseContentType; + } + + public static EntitySetResponse getInstance(ServiceRequest request, ContextURL contextURL, + boolean referencesOnly, ODataResponse response) throws ContentNegotiatorException, SerializerException { + EntityCollectionSerializerOptions options = request.getSerializerOptions( + EntityCollectionSerializerOptions.class, contextURL, referencesOnly); + return new EntitySetResponse(request.getServiceMetaData(),response, request.getSerializer(), options, + request.getResponseContentType(), request.getPreferences()); + } + + // write collection of entities + // TODO: server paging needs to be implemented. + public void writeReadEntitySet(EdmEntityType entityType, EntitySet entitySet) + throws SerializerException { + + assert (!isClosed()); + + if (entitySet == null) { + writeNotFound(true); + return; + } + + // write the whole collection to response + this.response.setContent(this.serializer.entityCollection(metadata, entityType, entitySet, this.options)); + writeOK(this.responseContentType.toContentTypeString()); + close(); + } + + @Override + public void accepts(ServiceResponseVisior visitor) throws ODataTranslatedException, + ODataApplicationException { + visitor.visit(this); + } +} diff --git a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/responses/MetadataResponse.java b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/responses/MetadataResponse.java new file mode 100644 index 000000000..055c0b0be --- /dev/null +++ b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/responses/MetadataResponse.java @@ -0,0 +1,62 @@ +/* + * 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.server.core.responses; + +import java.util.Map; + +import org.apache.olingo.commons.api.format.ContentType; +import org.apache.olingo.server.api.ODataApplicationException; +import org.apache.olingo.server.api.ODataResponse; +import org.apache.olingo.server.api.ODataTranslatedException; +import org.apache.olingo.server.api.ServiceMetadata; +import org.apache.olingo.server.api.serializer.ODataSerializer; +import org.apache.olingo.server.api.serializer.SerializerException; +import org.apache.olingo.server.core.ContentNegotiatorException; +import org.apache.olingo.server.core.ServiceRequest; + +public class MetadataResponse extends ServiceResponse { + private final ODataSerializer serializer; + private final ContentType responseContentType; + + public static MetadataResponse getInstance(ServiceRequest request, + ODataResponse response) throws ContentNegotiatorException, SerializerException { + return new MetadataResponse(request.getServiceMetaData(), response, request.getSerializer(), + request.getResponseContentType(), request.getPreferences()); + } + + private MetadataResponse(ServiceMetadata metadata, ODataResponse response, ODataSerializer serializer, + ContentType responseContentType, Map preferences) { + super(metadata, response, preferences); + this.serializer = serializer; + this.responseContentType = responseContentType; + } + + public void writeMetadata()throws ODataTranslatedException { + assert (!isClosed()); + this.response.setContent(this.serializer.metadataDocument(this.metadata)); + writeOK(this.responseContentType.toContentTypeString()); + close(); + } + + @Override + public void accepts(ServiceResponseVisior visitor) throws ODataTranslatedException, + ODataApplicationException { + visitor.visit(this); + } +} diff --git a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/responses/NoContentResponse.java b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/responses/NoContentResponse.java new file mode 100644 index 000000000..eb163658b --- /dev/null +++ b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/responses/NoContentResponse.java @@ -0,0 +1,100 @@ +/* + * 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.server.core.responses; + +import java.util.Collections; + +import org.apache.olingo.commons.api.http.HttpStatusCode; +import org.apache.olingo.server.api.ODataApplicationException; +import org.apache.olingo.server.api.ODataResponse; +import org.apache.olingo.server.api.ODataTranslatedException; +import org.apache.olingo.server.api.ServiceMetadata; + +public class NoContentResponse extends ServiceResponse { + + public NoContentResponse(ServiceMetadata metadata, ODataResponse response) { + super(metadata, response, Collections.EMPTY_MAP); + } + + // 200 + public void writeOK() { + this.response.setStatusCode(HttpStatusCode.OK.getStatusCode()); + close(); + } + + // 201 + public void writeCreated() { + this.response.setStatusCode(HttpStatusCode.CREATED.getStatusCode()); + close(); + } + + // 202 + public void writeAccepted() { + this.response.setStatusCode(HttpStatusCode.ACCEPTED.getStatusCode()); + close(); + } + + // 204 + public void writeNoContent() { + writeNoContent(true); + } + + // 304 + public void writeNotModified() { + this.response.setStatusCode(HttpStatusCode.NOT_MODIFIED.getStatusCode()); + close(); + } + + // error response codes + + // 404 + public void writeNotFound() { + writeNotFound(true); + } + + // 501 + public void writeNotImplemented() { + this.response.setStatusCode(HttpStatusCode.NOT_IMPLEMENTED.getStatusCode()); + close(); + } + + // 405 + public void writeMethodNotAllowed() { + this.response.setStatusCode(HttpStatusCode.METHOD_NOT_ALLOWED.getStatusCode()); + close(); + } + + // 410 + public void writeGone() { + this.response.setStatusCode(HttpStatusCode.GONE.getStatusCode()); + close(); + } + + // 412 + public void writePreConditionFailed() { + this.response.setStatusCode(HttpStatusCode.PRECONDITION_FAILED.getStatusCode()); + close(); + } + + @Override + public void accepts(ServiceResponseVisior visitor) throws ODataTranslatedException, + ODataApplicationException { + visitor.visit(this); + } +} diff --git a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/responses/PrimitiveValueResponse.java b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/responses/PrimitiveValueResponse.java new file mode 100644 index 000000000..005bfca9f --- /dev/null +++ b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/responses/PrimitiveValueResponse.java @@ -0,0 +1,105 @@ +/* + * 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.server.core.responses; + +import java.util.Map; + +import org.apache.olingo.commons.api.edm.EdmPrimitiveType; +import org.apache.olingo.commons.api.edm.EdmProperty; +import org.apache.olingo.commons.api.edm.EdmReturnType; +import org.apache.olingo.commons.api.http.HttpContentType; +import org.apache.olingo.server.api.ODataApplicationException; +import org.apache.olingo.server.api.ODataResponse; +import org.apache.olingo.server.api.ODataTranslatedException; +import org.apache.olingo.server.api.ServiceMetadata; +import org.apache.olingo.server.api.serializer.FixedFormatSerializer; +import org.apache.olingo.server.api.serializer.PrimitiveValueSerializerOptions; +import org.apache.olingo.server.api.serializer.SerializerException; +import org.apache.olingo.server.core.ServiceRequest; + +public class PrimitiveValueResponse extends ServiceResponse { + private final boolean returnCollection; + private EdmProperty type; + private EdmReturnType returnType; + private final FixedFormatSerializer serializer; + + public static PrimitiveValueResponse getInstance(ServiceRequest request, ODataResponse response, + boolean collection, EdmProperty type) { + FixedFormatSerializer serializer = request.getOdata().createFixedFormatSerializer(); + return new PrimitiveValueResponse(request.getServiceMetaData(), serializer, response, + collection, type, request.getPreferences()); + } + + public static PrimitiveValueResponse getInstance(ServiceRequest request, ODataResponse response, + boolean collection, EdmReturnType type) { + FixedFormatSerializer serializer = request.getOdata().createFixedFormatSerializer(); + return new PrimitiveValueResponse(request.getServiceMetaData(), serializer, response, + collection, type, request.getPreferences()); + } + + private PrimitiveValueResponse(ServiceMetadata metadata, FixedFormatSerializer serializer, + ODataResponse response, boolean collection, EdmProperty type, Map preferences) { + super(metadata, response, preferences); + this.returnCollection = collection; + this.type = type; + this.serializer = serializer; + } + + private PrimitiveValueResponse(ServiceMetadata metadata, FixedFormatSerializer serializer, + ODataResponse response, boolean collection, EdmReturnType type, + Map preferences) { + super(metadata, response, preferences); + this.returnCollection = collection; + this.returnType = type; + this.serializer = serializer; + } + + public void write(Object value) throws SerializerException { + if (value == null) { + writeNoContent(true); + return; + } + + if (this.type != null) { + PrimitiveValueSerializerOptions options = PrimitiveValueSerializerOptions.with() + .facetsFrom(this.type).build(); + + this.response.setContent(this.serializer.primitiveValue((EdmPrimitiveType) this.type.getType(), + value, options)); + } else { + PrimitiveValueSerializerOptions options = PrimitiveValueSerializerOptions.with() + .nullable(this.returnType.isNullable()).maxLength(this.returnType.getMaxLength()) + .precision(this.returnType.getPrecision()).scale(this.returnType.getScale()).build(); + this.response.setContent(this.serializer.primitiveValue( + (EdmPrimitiveType) this.returnType.getType(), value, options)); + } + + writeOK(HttpContentType.TEXT_PLAIN); + } + + public boolean isReturnCollection() { + return returnCollection; + } + + @Override + public void accepts(ServiceResponseVisior visitor) throws ODataTranslatedException, + ODataApplicationException { + visitor.visit(this); + } +} diff --git a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/responses/PropertyResponse.java b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/responses/PropertyResponse.java new file mode 100644 index 000000000..e6b951d39 --- /dev/null +++ b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/responses/PropertyResponse.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.server.core.responses; + +import java.util.Map; + +import org.apache.olingo.commons.api.data.ContextURL; +import org.apache.olingo.commons.api.data.Property; +import org.apache.olingo.commons.api.edm.EdmComplexType; +import org.apache.olingo.commons.api.edm.EdmPrimitiveType; +import org.apache.olingo.commons.api.edm.EdmType; +import org.apache.olingo.commons.api.edm.constants.EdmTypeKind; +import org.apache.olingo.commons.api.format.ContentType; +import org.apache.olingo.server.api.ODataApplicationException; +import org.apache.olingo.server.api.ODataResponse; +import org.apache.olingo.server.api.ODataTranslatedException; +import org.apache.olingo.server.api.ServiceMetadata; +import org.apache.olingo.server.api.serializer.ComplexSerializerOptions; +import org.apache.olingo.server.api.serializer.ODataSerializer; +import org.apache.olingo.server.api.serializer.PrimitiveSerializerOptions; +import org.apache.olingo.server.api.serializer.SerializerException; +import org.apache.olingo.server.core.ContentNegotiatorException; +import org.apache.olingo.server.core.ServiceRequest; + +public class PropertyResponse extends ServiceResponse { + private PrimitiveSerializerOptions primitiveOptions; + private ComplexSerializerOptions complexOptions; + private final ContentType responseContentType; + private final ODataSerializer serializer; + private final boolean collection; + + public static PropertyResponse getInstance(ServiceRequest request, ODataResponse response, + EdmType edmType, ContextURL contextURL, boolean collection) throws ContentNegotiatorException, + SerializerException { + if (edmType.getKind() == EdmTypeKind.PRIMITIVE) { + PrimitiveSerializerOptions options = request.getSerializerOptions( + PrimitiveSerializerOptions.class, contextURL, false); + ContentType type = request.getResponseContentType(); + return new PropertyResponse(request.getServiceMetaData(), request.getSerializer(), response, + options, type, collection, request.getPreferences()); + } + ComplexSerializerOptions options = request.getSerializerOptions(ComplexSerializerOptions.class, + contextURL, false); + ContentType type = request.getResponseContentType(); + return new PropertyResponse(request.getServiceMetaData(), request.getSerializer(), response, + options, type, collection, request.getPreferences()); + } + + private PropertyResponse(ServiceMetadata metadata, ODataSerializer serializer, + ODataResponse response, PrimitiveSerializerOptions options, ContentType contentType, + boolean collection, Map preferences) { + super(metadata, response, preferences); + this.serializer = serializer; + this.primitiveOptions = options; + this.responseContentType = contentType; + this.collection = collection; + } + + private PropertyResponse(ServiceMetadata metadata, ODataSerializer serializer, ODataResponse response, + ComplexSerializerOptions options, ContentType contentType, boolean collection, + Map preferences) { + super(metadata, response, preferences); + this.serializer = serializer; + this.complexOptions = options; + this.responseContentType = contentType; + this.collection = collection; + } + + public void writeProperty(EdmType edmType, Property property) throws SerializerException { + assert (!isClosed()); + + if (property == null) { + writeNotFound(true); + return; + } + + if (property.getValue() == null) { + writeNoContent(true); + return; + } + + if (edmType.getKind() == EdmTypeKind.PRIMITIVE) { + writePrimitiveProperty((EdmPrimitiveType) edmType, property); + } else { + writeComplexProperty((EdmComplexType) edmType, property); + } + } + + private void writeComplexProperty(EdmComplexType type, Property property) + throws SerializerException { + if (this.collection) { + this.response.setContent(this.serializer.complexCollection(this.metadata, type, property, + this.complexOptions)); + } else { + this.response.setContent(this.serializer.complex(this.metadata, type, property, + this.complexOptions)); + } + writeOK(this.responseContentType.toContentTypeString()); + close(); + } + + private void writePrimitiveProperty(EdmPrimitiveType type, Property property) + throws SerializerException { + if(this.collection) { + this.response.setContent(this.serializer.primitiveCollection(type, property, this.primitiveOptions)); + } else { + this.response.setContent(this.serializer.primitive(type, property, this.primitiveOptions)); + } + writeOK(this.responseContentType.toContentTypeString()); + close(); + } + + @Override + public void accepts(ServiceResponseVisior visitor) throws ODataTranslatedException, + ODataApplicationException { + visitor.visit(this); + } + + public void writePropertyUpdated() { + // spec says just success response; so either 200 or 204. 200 typically has + // payload + writeNoContent(true); + } + + public void writePropertyDeleted() { + writeNoContent(true); + } +} diff --git a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/responses/ServiceDocumentResponse.java b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/responses/ServiceDocumentResponse.java new file mode 100644 index 000000000..86c420b66 --- /dev/null +++ b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/responses/ServiceDocumentResponse.java @@ -0,0 +1,63 @@ +/* + * 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.server.core.responses; + +import java.util.Map; + +import org.apache.olingo.commons.api.format.ContentType; +import org.apache.olingo.server.api.ODataApplicationException; +import org.apache.olingo.server.api.ODataResponse; +import org.apache.olingo.server.api.ODataTranslatedException; +import org.apache.olingo.server.api.ServiceMetadata; +import org.apache.olingo.server.api.serializer.ODataSerializer; +import org.apache.olingo.server.api.serializer.SerializerException; +import org.apache.olingo.server.core.ContentNegotiatorException; +import org.apache.olingo.server.core.ServiceRequest; + +public class ServiceDocumentResponse extends ServiceResponse { + private final ODataSerializer serializer; + private final ContentType responseContentType; + + public static ServiceDocumentResponse getInstace(ServiceRequest request, ODataResponse respose, + ContentType responseContentType) throws ContentNegotiatorException, SerializerException { + return new ServiceDocumentResponse(request.getServiceMetaData(), respose, + request.getSerializer(), responseContentType, request.getPreferences()); + } + + private ServiceDocumentResponse(ServiceMetadata metadata, ODataResponse respose, + ODataSerializer serializer, ContentType responseContentType, Map preferences) { + super(metadata, respose, preferences); + this.serializer = serializer; + this.responseContentType = responseContentType; + } + + public void writeServiceDocument(String serviceRoot) + throws ODataTranslatedException { + assert (!isClosed()); + this.response.setContent(this.serializer.serviceDocument(this.metadata.getEdm(), serviceRoot)); + writeOK(this.responseContentType.toContentTypeString()); + close(); + } + + @Override + public void accepts(ServiceResponseVisior visitor) throws ODataTranslatedException, + ODataApplicationException { + visitor.visit(this); + } +} diff --git a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/responses/ServiceResponse.java b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/responses/ServiceResponse.java new file mode 100644 index 000000000..a3065515d --- /dev/null +++ b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/responses/ServiceResponse.java @@ -0,0 +1,119 @@ +/* + * 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.server.core.responses; + +import java.util.Map; + +import org.apache.olingo.commons.api.http.HttpHeader; +import org.apache.olingo.commons.api.http.HttpStatusCode; +import org.apache.olingo.server.api.ODataApplicationException; +import org.apache.olingo.server.api.ODataResponse; +import org.apache.olingo.server.api.ODataTranslatedException; +import org.apache.olingo.server.api.ServiceMetadata; + +public abstract class ServiceResponse { + protected ServiceMetadata metadata; + protected ODataResponse response; + protected Map preferences; + private boolean closed; + private boolean strictApplyPreferences = true; + + public ServiceResponse(ServiceMetadata metadata, ODataResponse response, + Map preferences) { + this.metadata = metadata; + this.response = response; + this.preferences = preferences; + } + + public ODataResponse getODataResponse() { + return this.response; + } + + protected boolean isClosed() { + return this.closed; + } + + protected void close() { + if (!this.closed) { + if (this.strictApplyPreferences) { + if (!preferences.isEmpty()) { + assert(this.response.getHeaders().get("Preference-Applied") != null); + } + } + this.closed = true; + } + } + + public void writeNoContent(boolean closeResponse) { + this.response.setStatusCode(HttpStatusCode.NO_CONTENT.getStatusCode()); + if (closeResponse) { + close(); + } + } + + public void writeNotFound(boolean closeResponse) { + response.setStatusCode(HttpStatusCode.NOT_FOUND.getStatusCode()); + if (closeResponse) { + close(); + } + } + + public void writeServerError(boolean closeResponse) { + response.setStatusCode(HttpStatusCode.INTERNAL_SERVER_ERROR.getStatusCode()); + if (closeResponse) { + close(); + } + } + + public void writeBadRequest(boolean closeResponse) { + response.setStatusCode(HttpStatusCode.BAD_REQUEST.getStatusCode()); + if (closeResponse) { + close(); + } + } + + public void writeOK(String contentType) { + this.response.setStatusCode(HttpStatusCode.OK.getStatusCode()); + this.response.setHeader(HttpHeader.CONTENT_TYPE, contentType); + } + + public void writeHeader(String key, String value) { + if ("Preference-Applied".equals(key)) { + String previous = this.response.getHeaders().get(key); + if (previous != null) { + value = previous+";"+value; + } + this.response.setHeader(key, value); + } else { + this.response.setHeader(key, value); + } + } + + /** + * When true; the "Preference-Applied" header is strictly checked. + * @param flag + */ + public void setStrictlyApplyPreferences(boolean flag) { + this.strictApplyPreferences = flag; + } + + public abstract void accepts(ServiceResponseVisior visitor) throws ODataTranslatedException, + ODataApplicationException; +} diff --git a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/responses/ServiceResponseVisior.java b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/responses/ServiceResponseVisior.java new file mode 100644 index 000000000..4f11cb8b6 --- /dev/null +++ b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/responses/ServiceResponseVisior.java @@ -0,0 +1,71 @@ +/* + * 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.server.core.responses; + +import org.apache.olingo.server.api.ODataApplicationException; +import org.apache.olingo.server.api.ODataTranslatedException; + +@SuppressWarnings("unused") +public class ServiceResponseVisior { + + public void visit(CountResponse response) throws ODataTranslatedException, + ODataApplicationException { + response.writeServerError(true); + } + + public void visit(EntityResponse response) throws ODataTranslatedException, + ODataApplicationException { + response.writeServerError(true); + } + + public void visit(MetadataResponse response) throws ODataTranslatedException, + ODataApplicationException { + response.writeServerError(true); + } + + public void visit(NoContentResponse response) throws ODataTranslatedException, + ODataApplicationException { + response.writeServerError(true); + } + + public void visit(PrimitiveValueResponse response) throws ODataTranslatedException, + ODataApplicationException { + response.writeServerError(true); + } + + public void visit(PropertyResponse response) throws ODataTranslatedException, + ODataApplicationException { + response.writeServerError(true); + } + + public void visit(ServiceDocumentResponse response) throws ODataTranslatedException, + ODataApplicationException { + response.writeServerError(true); + } + + public void visit(StreamResponse response) throws ODataTranslatedException, + ODataApplicationException { + response.writeServerError(true); + } + + public void visit(EntitySetResponse response) throws ODataTranslatedException, + ODataApplicationException { + response.writeServerError(true); + } +} diff --git a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/responses/StreamResponse.java b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/responses/StreamResponse.java new file mode 100644 index 000000000..ec7db032a --- /dev/null +++ b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/responses/StreamResponse.java @@ -0,0 +1,54 @@ +/* + * 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.server.core.responses; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.Collections; + +import org.apache.olingo.commons.api.format.ContentType; +import org.apache.olingo.server.api.ODataApplicationException; +import org.apache.olingo.server.api.ODataResponse; +import org.apache.olingo.server.api.ODataTranslatedException; +import org.apache.olingo.server.api.ServiceMetadata; + +public class StreamResponse extends ServiceResponse { + + public StreamResponse(ServiceMetadata metadata, ODataResponse response) { + super(metadata, response, Collections.EMPTY_MAP); + } + + public void writeStreamResponse(InputStream streamContent, ContentType contentType) { + this.response.setContent(streamContent); + writeOK(contentType.toContentTypeString()); + close(); + } + + public void writeBinaryResponse(byte[] streamContent, ContentType contentType) { + this.response.setContent(new ByteArrayInputStream(streamContent)); + writeOK(contentType.toContentTypeString()); + close(); + } + + @Override + public void accepts(ServiceResponseVisior visitor) throws ODataTranslatedException, + ODataApplicationException { + visitor.visit(this); + } +} diff --git a/lib/server-core-ext/src/test/java/org/apache/olingo/server/core/MetadataParserTest.java b/lib/server-core-ext/src/test/java/org/apache/olingo/server/core/MetadataParserTest.java new file mode 100644 index 000000000..9749a6cc5 --- /dev/null +++ b/lib/server-core-ext/src/test/java/org/apache/olingo/server/core/MetadataParserTest.java @@ -0,0 +1,185 @@ +/* + * 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.server.core; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.io.FileReader; +import java.util.List; + +import org.apache.olingo.commons.api.ODataException; +import org.apache.olingo.commons.api.edm.FullQualifiedName; +import org.apache.olingo.commons.api.edm.provider.Action; +import org.apache.olingo.commons.api.edm.provider.ActionImport; +import org.apache.olingo.commons.api.edm.provider.ComplexType; +import org.apache.olingo.commons.api.edm.provider.EdmProvider; +import org.apache.olingo.commons.api.edm.provider.EntitySet; +import org.apache.olingo.commons.api.edm.provider.EntityType; +import org.apache.olingo.commons.api.edm.provider.EnumType; +import org.apache.olingo.commons.api.edm.provider.Function; +import org.apache.olingo.commons.api.edm.provider.FunctionImport; +import org.apache.olingo.commons.api.edm.provider.NavigationPropertyBinding; +import org.apache.olingo.commons.api.edm.provider.Parameter; +import org.apache.olingo.commons.api.edm.provider.Property; +import org.apache.olingo.commons.api.edm.provider.Singleton; +import org.junit.Before; +import org.junit.Test; + +public class MetadataParserTest { + final String NS = "Microsoft.OData.SampleService.Models.TripPin"; + final FullQualifiedName NSF = new FullQualifiedName(NS); + + EdmProvider provider = null; + + @Before + public void setUp() throws Exception { + MetadataParser parser = new MetadataParser(); + provider = parser.buildEdmProvider(new FileReader("src/test/resources/trippin.xml")); + } + + @Test + public void testAction() throws ODataException { + // test action + List actions = provider.getActions(new FullQualifiedName(NS, "ResetDataSource")); + assertNotNull(actions); + assertEquals(1, actions.size()); + } + + @Test + public void testFunction() throws ODataException { + // test function + List functions = provider + .getFunctions(new FullQualifiedName(NS, "GetFavoriteAirline")); + assertNotNull(functions); + assertEquals(1, functions.size()); + assertEquals("GetFavoriteAirline", functions.get(0).getName()); + assertTrue(functions.get(0).isBound()); + assertTrue(functions.get(0).isComposable()); + assertEquals( + "person/Trips/PlanItems/Microsoft.OData.SampleService.Models.TripPin.Flight/Airline", + functions.get(0).getEntitySetPath()); + + List parameters = functions.get(0).getParameters(); + assertNotNull(parameters); + assertEquals(1, parameters.size()); + assertEquals("person", parameters.get(0).getName()); + assertEquals("Microsoft.OData.SampleService.Models.TripPin.Person",parameters.get(0).getType()); + assertFalse(parameters.get(0).isNullable()); + + assertNotNull(functions.get(0).getReturnType()); + assertEquals("Microsoft.OData.SampleService.Models.TripPin.Airline", + functions.get(0).getReturnType().getType()); + assertFalse(functions.get(0).getReturnType().isNullable()); + } + + @Test + public void testEnumType() throws ODataException { + // test enum type + EnumType enumType = provider.getEnumType(new FullQualifiedName(NS, "PersonGender")); + assertNotNull(enumType); + assertEquals("Male", enumType.getMembers().get(0).getName()); + assertEquals("Female", enumType.getMembers().get(1).getName()); + assertEquals("Unknown", enumType.getMembers().get(2).getName()); + assertEquals("0", enumType.getMembers().get(0).getValue()); + assertEquals("1", enumType.getMembers().get(1).getValue()); + assertEquals("2", enumType.getMembers().get(2).getValue()); + } + + @Test + public void testEntityType() throws ODataException { + // test Entity Type + EntityType et = provider.getEntityType(new FullQualifiedName(NS, "Photo")); + assertNotNull(et); + assertNotNull(et.getKey()); + assertEquals("Id", et.getKey().get(0).getName()); + assertTrue(et.hasStream()); + assertEquals("Id", et.getProperties().get(0).getName()); + assertEquals("Edm.Int64", et.getProperties().get(0).getType()); + assertEquals("Name", et.getProperties().get(1).getName()); + assertEquals("Edm.String", et.getProperties().get(1).getType()); + } + + @Test + public void testComplexType() throws ODataException { + // Test Complex Type + ComplexType ct = provider.getComplexType(new FullQualifiedName(NS, "City")); + assertNotNull(ct); + assertEquals(3, ct.getProperties().size()); + Property p = ct.getProperties().get(0); + assertEquals("CountryRegion", p.getName()); + assertEquals("Edm.String", p.getType()); + assertEquals(false, p.isNullable()); + + ct = provider.getComplexType(new FullQualifiedName(NS, "Location")); + assertNotNull(ct); + + ct = provider.getComplexType(new FullQualifiedName(NS, "EventLocation")); + assertNotNull(ct); + } + + @Test + public void testEntitySet() throws Exception { + EntitySet es = provider.getEntitySet(NSF, "People"); + assertNotNull(es); + assertEquals("Microsoft.OData.SampleService.Models.TripPin.Person",es.getType()); + + List bindings = es.getNavigationPropertyBindings(); + assertNotNull(bindings); + assertEquals(6, bindings.size()); + assertEquals("Microsoft.OData.SampleService.Models.TripPin.Flight/From", bindings.get(2) + .getPath()); + assertEquals("Airports", bindings.get(2).getTarget()); + } + + @Test + public void testFunctionImport() throws Exception { + FunctionImport fi = provider.getFunctionImport(NSF, "GetNearestAirport"); + assertNotNull(fi); + assertEquals("Microsoft.OData.SampleService.Models.TripPin.GetNearestAirport", fi.getFunction()); + assertEquals("Airports", fi.getEntitySet()); + assertTrue(fi.isIncludeInServiceDocument()); + } + + @Test + public void testActionImport() throws Exception { + ActionImport ai = provider.getActionImport(NSF, "ResetDataSource"); + assertNotNull(ai); + assertEquals("Microsoft.OData.SampleService.Models.TripPin.ResetDataSource", ai.getAction()); + assertNull(ai.getEntitySet()); + } + + @Test + public void testSingleton() throws Exception { + Singleton single = this.provider.getSingleton(NSF, "Me"); + assertNotNull(single); + + assertEquals("Microsoft.OData.SampleService.Models.TripPin.Person",single.getType()); + + List bindings = single.getNavigationPropertyBindings(); + assertNotNull(bindings); + assertEquals(6, bindings.size()); + assertEquals("Microsoft.OData.SampleService.Models.TripPin.Flight/From", bindings.get(2).getPath()); + assertEquals("Airports", bindings.get(2).getTarget()); + + } +} diff --git a/lib/server-core-ext/src/test/java/org/apache/olingo/server/core/ServiceDispatcherTest.java b/lib/server-core-ext/src/test/java/org/apache/olingo/server/core/ServiceDispatcherTest.java new file mode 100644 index 000000000..16caa1ae4 --- /dev/null +++ b/lib/server-core-ext/src/test/java/org/apache/olingo/server/core/ServiceDispatcherTest.java @@ -0,0 +1,417 @@ +/* + * 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.server.core; + +import static org.junit.Assert.assertEquals; + +import java.io.FileReader; +import java.io.IOException; +import java.net.URI; +import java.util.Collections; +import java.util.List; + +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.olingo.commons.api.edm.provider.EdmProvider; +import org.apache.olingo.commons.api.http.HttpMethod; +import org.apache.olingo.server.api.OData; +import org.apache.olingo.server.api.ODataHttpHandler; +import org.apache.olingo.server.api.ServiceMetadata; +import org.apache.olingo.server.core.requests.ActionRequest; +import org.apache.olingo.server.core.requests.DataRequest; +import org.apache.olingo.server.core.requests.FunctionRequest; +import org.apache.olingo.server.core.requests.MediaRequest; +import org.apache.olingo.server.core.requests.MetadataRequest; +import org.apache.olingo.server.core.responses.CountResponse; +import org.apache.olingo.server.core.responses.EntityResponse; +import org.apache.olingo.server.core.responses.EntitySetResponse; +import org.apache.olingo.server.core.responses.MetadataResponse; +import org.apache.olingo.server.core.responses.NoContentResponse; +import org.apache.olingo.server.core.responses.PrimitiveValueResponse; +import org.apache.olingo.server.core.responses.PropertyResponse; +import org.apache.olingo.server.core.responses.StreamResponse; +import org.apache.olingo.server.example.TripPinServiceTest; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; + +public class ServiceDispatcherTest { + private Server server; + + public class SampleODataServlet extends HttpServlet { + private final ServiceHandler handler; // must be stateless + private final EdmProvider provider; // must be stateless + + public SampleODataServlet(ServiceHandler handler, EdmProvider provider) { + this.handler = handler; + this.provider = provider; + } + + @Override + public void service(HttpServletRequest request, HttpServletResponse response) + throws IOException { + OData odata = OData4Impl.newInstance(); + ServiceMetadata metadata = odata.createServiceMetadata(this.provider, Collections.EMPTY_LIST); + + ODataHttpHandler handler = odata.createHandler(metadata); + + handler.register(this.handler); + handler.process(request, response); + } + } + + public int beforeTest(ServiceHandler serviceHandler) throws Exception { + MetadataParser parser = new MetadataParser(); + EdmProvider edmProvider = parser.buildEdmProvider(new FileReader( + "src/test/resources/trippin.xml")); + + this.server = new Server(); + + ServerConnector connector = new ServerConnector(this.server); + this.server.setConnectors(new Connector[] { connector }); + + ServletContextHandler context = new ServletContextHandler(); + context.setContextPath("/trippin"); + context + .addServlet(new ServletHolder(new SampleODataServlet(serviceHandler, edmProvider)), "/*"); + this.server.setHandler(context); + this.server.start(); + + return connector.getLocalPort(); + } + + public void afterTest() throws Exception { + this.server.stop(); + } + + interface TestResult { + void validate() throws Exception; + } + + private void helpGETTest(ServiceHandler handler, String path, TestResult validator) + throws Exception { + int port = beforeTest(handler); + HttpClient http = new HttpClient(); + http.start(); + http.GET("http://localhost:" + port + "/" + path); + validator.validate(); + afterTest(); + } + + private void helpTest(ServiceHandler handler, String path, String method, String payload, + TestResult validator) throws Exception { + int port = beforeTest(handler); + HttpClient http = new HttpClient(); + http.start(); + String editUrl = "http://localhost:" + port + "/" + path; + http.newRequest(editUrl).method(method) + .header("Content-Type", "application/json;odata.metadata=minimal") + .content(TripPinServiceTest.content(payload)).send(); + validator.validate(); + afterTest(); + } + + @Test + public void testMetadata() throws Exception { + final ServiceHandler handler = Mockito.mock(ServiceHandler.class); + helpGETTest(handler, "trippin/$metadata", new TestResult() { + @Override + public void validate() throws Exception { + ArgumentCaptor arg1 = ArgumentCaptor.forClass(MetadataRequest.class); + ArgumentCaptor arg2 = ArgumentCaptor.forClass(MetadataResponse.class); + Mockito.verify(handler).readMetadata(arg1.capture(), arg2.capture()); + } + }); + } + + @Test + public void testEntitySet() throws Exception { + final ServiceHandler handler = Mockito.mock(ServiceHandler.class); + helpGETTest(handler, "trippin/Airports", new TestResult() { + @Override + public void validate() throws Exception { + ArgumentCaptor arg1 = ArgumentCaptor.forClass(DataRequest.class); + ArgumentCaptor arg2 = ArgumentCaptor.forClass(EntityResponse.class); + Mockito.verify(handler).read(arg1.capture(), arg2.capture()); + + DataRequest request = arg1.getValue(); + // Need toString on ContextURL class + // assertEquals("", + // request.getContextURL(request.getOdata()).toString()); + assertEquals("application/json;odata.metadata=minimal", request.getResponseContentType() + .toContentTypeString()); + } + }); + } + + @Test + public void testEntitySetCount() throws Exception { + final ServiceHandler handler = Mockito.mock(ServiceHandler.class); + helpGETTest(handler, "trippin/Airports/$count", new TestResult() { + @Override + public void validate() throws Exception { + ArgumentCaptor arg1 = ArgumentCaptor.forClass(DataRequest.class); + ArgumentCaptor arg2 = ArgumentCaptor.forClass(CountResponse.class); + Mockito.verify(handler).read(arg1.capture(), arg2.capture()); + + DataRequest request = arg1.getValue(); + // Need toString on ContextURL class + // assertEquals("", + // request.getContextURL(request.getOdata()).toString()); + assertEquals("text/plain", request.getResponseContentType().toContentTypeString()); + } + }); + } + + @Test + public void testEntity() throws Exception { + final ServiceHandler handler = Mockito.mock(ServiceHandler.class); + helpGETTest(handler, "trippin/Airports('0')", new TestResult() { + @Override + public void validate() throws Exception { + ArgumentCaptor arg1 = ArgumentCaptor.forClass(DataRequest.class); + ArgumentCaptor arg2 = ArgumentCaptor.forClass(EntityResponse.class); + Mockito.verify(handler).read(arg1.capture(), arg2.capture()); + + DataRequest request = arg1.getValue(); + assertEquals(1, request.getUriResourceEntitySet().getKeyPredicates().size()); + assertEquals("application/json;odata.metadata=minimal", request.getResponseContentType() + .toContentTypeString()); + } + }); + } + + @Test + public void testReadProperty() throws Exception { + final ServiceHandler handler = Mockito.mock(ServiceHandler.class); + helpGETTest(handler, "trippin/Airports('0')/IataCode", new TestResult() { + @Override + public void validate() throws Exception { + ArgumentCaptor arg1 = ArgumentCaptor.forClass(DataRequest.class); + ArgumentCaptor arg2 = ArgumentCaptor.forClass(PropertyResponse.class); + Mockito.verify(handler).read(arg1.capture(), arg2.capture()); + + DataRequest request = arg1.getValue(); + assertEquals(true, request.isPropertyRequest()); + assertEquals(false, request.isPropertyComplex()); + assertEquals(1, request.getUriResourceEntitySet().getKeyPredicates().size()); + assertEquals("application/json;odata.metadata=minimal", request.getResponseContentType() + .toContentTypeString()); + } + }); + } + + @Test + public void testReadComplexProperty() throws Exception { + final ServiceHandler handler = Mockito.mock(ServiceHandler.class); + helpGETTest(handler, "trippin/Airports('0')/Location", new TestResult() { + @Override + public void validate() throws Exception { + ArgumentCaptor arg1 = ArgumentCaptor.forClass(DataRequest.class); + ArgumentCaptor arg2 = ArgumentCaptor.forClass(PropertyResponse.class); + Mockito.verify(handler).read(arg1.capture(), arg2.capture()); + + DataRequest request = arg1.getValue(); + assertEquals(true, request.isPropertyRequest()); + assertEquals(true, request.isPropertyComplex()); + assertEquals(1, request.getUriResourceEntitySet().getKeyPredicates().size()); + assertEquals("application/json;odata.metadata=minimal", request.getResponseContentType() + .toContentTypeString()); + } + }); + } + + @Test + public void testReadProperty$Value() throws Exception { + final ServiceHandler handler = Mockito.mock(ServiceHandler.class); + helpGETTest(handler, "trippin/Airports('0')/IataCode/$value", new TestResult() { + @Override + public void validate() throws Exception { + ArgumentCaptor arg1 = ArgumentCaptor.forClass(DataRequest.class); + ArgumentCaptor arg2 = ArgumentCaptor + .forClass(PrimitiveValueResponse.class); + Mockito.verify(handler).read(arg1.capture(), arg2.capture()); + + DataRequest request = arg1.getValue(); + assertEquals(true, request.isPropertyRequest()); + assertEquals(false, request.isPropertyComplex()); + assertEquals(1, request.getUriResourceEntitySet().getKeyPredicates().size()); + assertEquals("text/plain", request.getResponseContentType().toContentTypeString()); + } + }); + } + + @Test + public void testReadPropertyRef() throws Exception { + final ServiceHandler handler = Mockito.mock(ServiceHandler.class); + helpGETTest(handler, "trippin/Airports('0')/IataCode/$value", new TestResult() { + @Override + public void validate() throws Exception { + ArgumentCaptor arg1 = ArgumentCaptor.forClass(DataRequest.class); + ArgumentCaptor arg2 = ArgumentCaptor + .forClass(PrimitiveValueResponse.class); + Mockito.verify(handler).read(arg1.capture(), arg2.capture()); + + DataRequest request = arg1.getValue(); + assertEquals(true, request.isPropertyRequest()); + assertEquals(false, request.isPropertyComplex()); + assertEquals(1, request.getUriResourceEntitySet().getKeyPredicates().size()); + assertEquals("text/plain", request.getResponseContentType().toContentTypeString()); + } + }); + } + + @Test + public void testFunctionImport() throws Exception { + final ServiceHandler handler = Mockito.mock(ServiceHandler.class); + helpGETTest(handler, "trippin/GetNearestAirport(lat=12.11,lon=34.23)", new TestResult() { + @Override + public void validate() throws Exception { + ArgumentCaptor arg1 = ArgumentCaptor.forClass(FunctionRequest.class); + ArgumentCaptor arg3 = ArgumentCaptor.forClass(PropertyResponse.class); + ArgumentCaptor arg2 = ArgumentCaptor.forClass(HttpMethod.class); + Mockito.verify(handler).invoke(arg1.capture(), arg2.capture(), arg3.capture()); + + FunctionRequest request = arg1.getValue(); + } + }); + } + + @Test + public void testActionImport() throws Exception { + final ServiceHandler handler = Mockito.mock(ServiceHandler.class); + helpTest(handler, "trippin/ResetDataSource", "POST", "", new TestResult() { + @Override + public void validate() throws Exception { + ArgumentCaptor arg1 = ArgumentCaptor.forClass(ActionRequest.class); + ArgumentCaptor arg2 = ArgumentCaptor.forClass(NoContentResponse.class); + Mockito.verify(handler).invoke(arg1.capture(), Mockito.anyString(), arg2.capture()); + + ActionRequest request = arg1.getValue(); + } + }); + } + + @Test + public void testReadMedia() throws Exception { + final ServiceHandler handler = Mockito.mock(ServiceHandler.class); + helpGETTest(handler, "trippin/Photos(1)/$value", new TestResult() { + @Override + public void validate() throws Exception { + ArgumentCaptor arg1 = ArgumentCaptor.forClass(MediaRequest.class); + ArgumentCaptor arg2 = ArgumentCaptor.forClass(StreamResponse.class); + Mockito.verify(handler).readMediaStream(arg1.capture(), arg2.capture()); + + MediaRequest request = arg1.getValue(); + assertEquals("application/octet-stream", request.getResponseContentType() + .toContentTypeString()); + } + }); + } + + @Test + public void testReadNavigation() throws Exception { + final ServiceHandler handler = Mockito.mock(ServiceHandler.class); + helpGETTest(handler, "trippin/People('russelwhyte')/Friends", new TestResult() { + @Override + public void validate() throws Exception { + ArgumentCaptor arg1 = ArgumentCaptor.forClass(DataRequest.class); + ArgumentCaptor arg2 = ArgumentCaptor.forClass(EntitySetResponse.class); + Mockito.verify(handler).read(arg1.capture(), arg2.capture()); + + DataRequest request = arg1.getValue(); + assertEquals("application/json;odata.metadata=minimal", request.getResponseContentType() + .toContentTypeString()); + } + }); + } + + @Test + public void testReadReference() throws Exception { + final ServiceHandler handler = Mockito.mock(ServiceHandler.class); + helpGETTest(handler, "trippin/People('russelwhyte')/Friends/$ref", new TestResult() { + @Override + public void validate() throws Exception { + ArgumentCaptor arg1 = ArgumentCaptor.forClass(DataRequest.class); + ArgumentCaptor arg2 = ArgumentCaptor.forClass(EntitySetResponse.class); + Mockito.verify(handler).read(arg1.capture(), arg2.capture()); + + DataRequest request = arg1.getValue(); + assertEquals("application/json;odata.metadata=minimal", request.getResponseContentType() + .toContentTypeString()); + } + }); + } + + @Test + public void testWriteReferenceCollection() throws Exception { + String payload = "{\n" + "\"@odata.id\": \"/Photos(11)\"\n" + "}"; + + final ServiceHandler handler = Mockito.mock(ServiceHandler.class); + helpTest(handler, "trippin/People('russelwhyte')/Friends/$ref", "POST", payload, + new TestResult() { + @Override + public void validate() throws Exception { + ArgumentCaptor arg1 = ArgumentCaptor.forClass(DataRequest.class); + ArgumentCaptor arg2 = ArgumentCaptor.forClass(String.class); + ArgumentCaptor arg3 = ArgumentCaptor.forClass(List.class); + ArgumentCaptor arg4 = ArgumentCaptor + .forClass(NoContentResponse.class); + Mockito.verify(handler).addReference(arg1.capture(), arg2.capture(), arg3.capture(), + arg4.capture()); + + DataRequest request = arg1.getValue(); + assertEquals("application/json;odata.metadata=minimal", request + .getResponseContentType().toContentTypeString()); + } + }); + } + + @Test + public void testWriteReference() throws Exception { + String payload = "{\n" + "\"@odata.id\": \"/Photos(11)\"\n" + "}"; + + final ServiceHandler handler = Mockito.mock(ServiceHandler.class); + helpTest(handler, "trippin/People('russelwhyte')/Friends('someone')/Photo/$ref", "PUT", payload, + new TestResult() { + @Override + public void validate() throws Exception { + ArgumentCaptor arg1 = ArgumentCaptor.forClass(DataRequest.class); + ArgumentCaptor arg2 = ArgumentCaptor.forClass(String.class); + ArgumentCaptor arg3 = ArgumentCaptor.forClass(URI.class); + ArgumentCaptor arg4 = ArgumentCaptor + .forClass(NoContentResponse.class); + Mockito.verify(handler).updateReference(arg1.capture(), arg2.capture(), arg3.capture(), + arg4.capture()); + + DataRequest request = arg1.getValue(); + assertEquals("application/json;odata.metadata=minimal", request + .getResponseContentType().toContentTypeString()); + } + }); + } +} diff --git a/lib/server-core-ext/src/test/java/org/apache/olingo/server/example/TripPinDataModel.java b/lib/server-core-ext/src/test/java/org/apache/olingo/server/example/TripPinDataModel.java new file mode 100644 index 000000000..904f4d86a --- /dev/null +++ b/lib/server-core-ext/src/test/java/org/apache/olingo/server/example/TripPinDataModel.java @@ -0,0 +1,843 @@ +/* + * 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.server.example; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.UUID; + +import org.apache.olingo.commons.api.data.Entity; +import org.apache.olingo.commons.api.data.EntitySet; +import org.apache.olingo.commons.api.data.Link; +import org.apache.olingo.commons.api.data.Property; +import org.apache.olingo.commons.api.edm.EdmEntityContainer; +import org.apache.olingo.commons.api.edm.EdmEntitySet; +import org.apache.olingo.commons.api.edm.EdmEntityType; +import org.apache.olingo.commons.api.edm.EdmPrimitiveType; +import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeException; +import org.apache.olingo.commons.api.edm.EdmProperty; +import org.apache.olingo.commons.api.edm.EdmType; +import org.apache.olingo.commons.api.edm.FullQualifiedName; +import org.apache.olingo.commons.api.edm.constants.EdmTypeKind; +import org.apache.olingo.commons.core.data.EntityImpl; +import org.apache.olingo.commons.core.data.EntitySetImpl; +import org.apache.olingo.commons.core.data.LinkImpl; +import org.apache.olingo.server.api.ODataApplicationException; +import org.apache.olingo.server.api.ServiceMetadata; +import org.apache.olingo.server.api.deserializer.DeserializerException; +import org.apache.olingo.server.api.uri.UriParameter; +import org.apache.olingo.server.api.uri.UriResourceNavigation; +import org.apache.olingo.server.core.deserializer.json.ODataJsonDeserializer; + +import com.fasterxml.jackson.databind.ObjectMapper; + +public class TripPinDataModel { + private final ServiceMetadata metadata; + private HashMap entitySetMap; + private Map tripLinks; + private Map peopleLinks; + private Map flightLinks; + + public TripPinDataModel(ServiceMetadata metadata) throws Exception { + this.metadata = metadata; + loadData(); + } + + public void loadData() throws Exception { + this.entitySetMap = new HashMap(); + this.tripLinks = new HashMap(); + this.peopleLinks = new HashMap(); + this.flightLinks = new HashMap(); + + EdmEntityContainer ec = metadata.getEdm().getEntityContainer(null); + for (EdmEntitySet edmEntitySet : ec.getEntitySets()) { + String entitySetName = edmEntitySet.getName(); + EntitySet set = loadEnities(entitySetName, edmEntitySet.getEntityType()); + if (set != null) { + this.entitySetMap.put(entitySetName, set); + } + } + + EdmEntityType type = metadata.getEdm().getEntityType( + new FullQualifiedName("Microsoft.OData.SampleService.Models.TripPin", "Trip")); + this.entitySetMap.put("Trip", loadEnities("Trip", type)); + + type = metadata.getEdm().getEntityType( + new FullQualifiedName("Microsoft.OData.SampleService.Models.TripPin", "Flight")); + this.entitySetMap.put("Flight", loadEnities("Flight", type)); + + type = metadata.getEdm().getEntityType( + new FullQualifiedName("Microsoft.OData.SampleService.Models.TripPin", "Event")); + this.entitySetMap.put("Event", loadEnities("Event", type)); + + ObjectMapper mapper = new ObjectMapper(); + Map tripLinks = mapper.readValue(new FileInputStream(new File( + "src/test/resources/trip-links.json")), Map.class); + for (Object link : (ArrayList) tripLinks.get("value")) { + Map map = (Map) link; + this.tripLinks.put((Integer) map.get("TripId"), map); + } + + Map peopleLinks = mapper.readValue(new FileInputStream(new File( + "src/test/resources/people-links.json")), Map.class); + for (Object link : (ArrayList) peopleLinks.get("value")) { + Map map = (Map) link; + this.peopleLinks.put((String) map.get("UserName"), map); + } + + Map flightLinks = mapper.readValue(new FileInputStream(new File( + "src/test/resources/flight-links.json")), Map.class); + for (Object link : (ArrayList) flightLinks.get("value")) { + Map map = (Map) link; + this.flightLinks.put((Integer) map.get("PlanItemId"), map); + } + } + + private EntitySet loadEnities(String entitySetName, EdmEntityType type) { + try { + ODataJsonDeserializer deserializer = new ODataJsonDeserializer(); + + EntitySet set = deserializer.entityCollection(new FileInputStream(new File( + "src/test/resources/" + entitySetName.toLowerCase() + ".json")), type); + // TODO: the count needs to be part of deserializer + set.setCount(set.getEntities().size()); + for (Entity entity : set.getEntities()) { + ((EntityImpl) entity).setETag(UUID.randomUUID().toString()); + ((EntityImpl) entity).setId(new URI(TripPinHandler.buildLocation(entity, entitySetName, + type))); + ((EntityImpl) entity).setType(type.getFullQualifiedName().getFullQualifiedNameAsString()); + } + return set; + } catch (FileNotFoundException e) { + // keep going + e.printStackTrace(); + } catch (DeserializerException e) { + // keep going + e.printStackTrace(); + } catch (URISyntaxException e) { + // keep going + e.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + public EntitySet getEntitySet(String name) { + return getEntitySet(name, -1, -1); + } + + public EntitySet getEntitySet(String name, int skip, int pageSize) { + EntitySet set = this.entitySetMap.get(name); + if (set == null) { + return null; + } + + EntitySetImpl modifiedES = new EntitySetImpl(); + int i = 0; + for (Entity e : set.getEntities()) { + if (skip >= 0 && i >= skip && modifiedES.getEntities().size() < pageSize) { + modifiedES.getEntities().add(e); + } + i++; + } + modifiedES.setCount(i); + set.setCount(i); + + if (skip == -1 && pageSize == -1) { + return set; + } + return modifiedES; + } + + private List getMatch(UriParameter param, List es) + throws ODataApplicationException { + ArrayList list = new ArrayList(); + for (Entity entity : es) { + + EdmEntityType entityType = this.metadata.getEdm().getEntityType( + new FullQualifiedName(entity.getType())); + + EdmProperty property = (EdmProperty) entityType.getProperty(param.getName()); + EdmType type = property.getType(); + if (type.getKind() == EdmTypeKind.PRIMITIVE) { + Object match = readPrimitiveValue(property, param.getText()); + Property entityValue = entity.getProperty(param.getName()); + if (match.equals(entityValue.asPrimitive())) { + list.add(entity); + } + } else { + throw new RuntimeException("Can not compare complex objects"); + } + } + return list; + } + + static Object readPrimitiveValue(EdmProperty edmProperty, String value) + throws ODataApplicationException { + if (value == null) { + return null; + } + try { + if (value.startsWith("'") && value.endsWith("'")) { + value = value.substring(1,value.length()-1); + } + EdmPrimitiveType edmPrimitiveType = (EdmPrimitiveType) edmProperty.getType(); + Class javaClass = getJavaClassForPrimitiveType(edmProperty, edmPrimitiveType); + return edmPrimitiveType.valueOfString(value, edmProperty.isNullable(), + edmProperty.getMaxLength(), edmProperty.getPrecision(), edmProperty.getScale(), + edmProperty.isUnicode(), javaClass); + } catch (EdmPrimitiveTypeException e) { + throw new ODataApplicationException("Invalid value: " + value + " for property: " + + edmProperty.getName(), 500, Locale.getDefault()); + } + } + + static Class getJavaClassForPrimitiveType(EdmProperty edmProperty, EdmPrimitiveType edmPrimitiveType) { + Class javaClass = null; + if (edmProperty.getMapping() != null && edmProperty.getMapping().getMappedJavaClass() != null) { + javaClass = edmProperty.getMapping().getMappedJavaClass(); + } else { + javaClass = edmPrimitiveType.getDefaultType(); + } + + edmPrimitiveType.getDefaultType(); + return javaClass; + } + + public Entity getEntity(String name, List keys) throws ODataApplicationException { + EntitySet es = getEntitySet(name); + return getEntity(es, keys); + } + + public Entity getEntity(EntitySet es, List keys) throws ODataApplicationException { + List search = es.getEntities(); + for (UriParameter param : keys) { + search = getMatch(param, search); + } + if (search.isEmpty()) { + return null; + } + return search.get(0); + } + + private EntitySet getFriends(String userName) { + Map map = this.peopleLinks.get(userName); + if (map == null) { + return null; + } + ArrayList friends = (ArrayList) map.get("Friends"); + EntitySet set = getEntitySet("People"); + + EntitySetImpl result = new EntitySetImpl(); + int i = 0; + if (friends != null) { + for (String friend : friends) { + for (Entity e : set.getEntities()) { + if (e.getProperty("UserName").getValue().equals(friend)) { + result.getEntities().add(e); + i++; + break; + } + } + } + } + result.setCount(i); + return result; + } + + private EntitySet getTrips(String userName) { + Map map = this.peopleLinks.get(userName); + if (map == null) { + return null; + } + + ArrayList trips = (ArrayList) map.get("Trips"); + EntitySet set = getEntitySet("Trip"); + + EntitySetImpl result = new EntitySetImpl(); + int i = 0; + if (trips != null) { + for (int trip : trips) { + for (Entity e : set.getEntities()) { + if (e.getProperty("TripId").getValue().equals(trip)) { + result.getEntities().add(e); + i++; + break; + } + } + } + } + result.setCount(i); + return result; + } + + private Entity getPhoto(String userName) { + Map map = this.peopleLinks.get(userName); + if (map == null) { + return null; + } + + Integer photoID = (Integer) map.get("Photo"); + EntitySet set = getEntitySet("Photos"); + if (photoID != null) { + for (Entity e : set.getEntities()) { + if (e.getProperty("Id").getValue().equals(photoID.longValue())) { + return e; + } + } + } + return null; + } + + private EntitySet getPlanItems(int tripId, EntitySetImpl result) { + getFlights(tripId, result); + getEvents(tripId, result); + return result; + } + + private EntitySet getEvents(int tripId, EntitySetImpl result) { + Map map = this.tripLinks.get(tripId); + if (map == null) { + return null; + } + + ArrayList events = (ArrayList) map.get("Events"); + EntitySet set = getEntitySet("Event"); + int i = result.getEntities().size(); + if (events != null) { + for (int event : events) { + for (Entity e : set.getEntities()) { + if (e.getProperty("PlanItemId").getValue().equals(event)) { + result.getEntities().add(e); + i++; + break; + } + } + } + } + result.setCount(i); + return result; + } + + private EntitySet getFlights(int tripId, EntitySetImpl result) { + Map map = this.tripLinks.get(tripId); + if (map == null) { + return null; + } + + ArrayList flights = (ArrayList) map.get("Flights"); + EntitySet set = getEntitySet("Flight"); + int i = result.getEntities().size(); + if (flights != null) { + for (int flight : flights) { + for (Entity e : set.getEntities()) { + if (e.getProperty("PlanItemId").getValue().equals(flight)) { + result.getEntities().add(e); + i++; + break; + } + } + } + } + result.setCount(i); + return result; + } + + private EntitySet getTripPhotos(int tripId) { + Map map = this.tripLinks.get(tripId); + if (map == null) { + return null; + } + + ArrayList photos = (ArrayList) map.get("Photos"); + + EntitySet set = getEntitySet("Photos"); + EntitySetImpl result = new EntitySetImpl(); + int i = 0; + if (photos != null) { + for (int photo : photos) { + for (Entity e : set.getEntities()) { + if (e.getProperty("Id").getValue().equals(photo)) { + result.getEntities().add(e); + i++; + break; + } + } + } + } + result.setCount(i); + return result; + } + + private Entity getFlightFrom(int flighID) { + Map map = this.flightLinks.get(flighID); + if (map == null) { + return null; + } + + String from = (String) map.get("From"); + EntitySet set = getEntitySet("Airports"); + + if (from != null) { + for (Entity e : set.getEntities()) { + if (e.getProperty("IataCode").getValue().equals(from)) { + return e; + } + } + } + return null; + } + + private Entity getFlightTo(int flighID) { + Map map = this.flightLinks.get(flighID); + if (map == null) { + return null; + } + + String to = (String) map.get("To"); + EntitySet set = getEntitySet("Airports"); + + if (to != null) { + for (Entity e : set.getEntities()) { + if (e.getProperty("IataCode").getValue().equals(to)) { + return e; + } + } + } + return null; + } + + private Entity getFlightAirline(int flighID) { + Map map = this.flightLinks.get(flighID); + if (map == null) { + return null; + } + + String airline = (String) map.get("Airline"); + EntitySet set = getEntitySet("Airlines"); + + if (airline != null) { + for (Entity e : set.getEntities()) { + if (e.getProperty("AirlineCode").getValue().equals(airline)) { + return e; + } + } + } + return null; + } + + public void addNavigationLink(String navigation, Entity parentEntity, Entity childEntity) { + + EdmEntityType type = this.metadata.getEdm().getEntityType( + new FullQualifiedName(parentEntity.getType())); + String key = type.getKeyPredicateNames().get(0); + if (type.getName().equals("Person") && navigation.equals("Friends")) { + Map map = this.peopleLinks.get(parentEntity.getProperty(key).getValue()); + if (map == null) { + map = new HashMap(); + this.peopleLinks.put((String) parentEntity.getProperty(key).getValue(), map); + } + + ArrayList friends = (ArrayList) map.get("Friends"); + if (friends == null) { + friends = new ArrayList(); + map.put("Friends", friends); + } + friends.add((String) childEntity.getProperty(key).getValue()); + setLink(parentEntity, navigation, childEntity); + } else if (type.getName().equals("Person") && navigation.equals("Trips")) { + Map map = this.peopleLinks.get(parentEntity.getProperty(key).getValue()); + if (map == null) { + map = new HashMap(); + this.peopleLinks.put((String) parentEntity.getProperty(key).getValue(), map); + } + + ArrayList trips = (ArrayList) map.get("Trips"); + if (trips == null) { + trips = new ArrayList(); + map.put("Trips", trips); + } + trips.add((Integer) childEntity.getProperty(key).getValue()); + setLink(parentEntity, navigation, childEntity); + } else if (type.getName().equals("Person") && navigation.equals("Photo")) { + Map map = this.peopleLinks.get(parentEntity.getProperty(key).getValue()); + if (map == null) { + map = new HashMap(); + this.peopleLinks.put((String) parentEntity.getProperty(key).getValue(), map); + } + map.put("Photo", childEntity.getProperty(key).getValue()); + setLink(parentEntity, navigation, childEntity); + } else if (type.getName().equals("Trip") && navigation.equals("PlanItems")) { + Map map = this.tripLinks.get(parentEntity.getProperty(key).getValue()); + if (map == null) { + map = new HashMap(); + this.tripLinks.put((Integer) parentEntity.getProperty(key).getValue(), map); + } + if (childEntity.getType().equals("Flight")) { + ArrayList flights = (ArrayList) map.get("Flights"); + if (flights == null) { + flights = new ArrayList(); + map.put("Flights", flights); + } + flights.add((Integer) childEntity.getProperty(key).getValue()); + } else { + ArrayList events = (ArrayList) map.get("Events"); + if (events == null) { + events = new ArrayList(); + map.put("Events", events); + } + events.add((Integer) childEntity.getProperty(key).getValue()); + } + setLink(parentEntity, navigation, childEntity); + } else if (type.getName().equals("Trip") && navigation.equals("Photo")) { + Map map = this.tripLinks.get(parentEntity.getProperty(key).getValue()); + if (map == null) { + map = new HashMap(); + this.tripLinks.put((Integer) parentEntity.getProperty(key).getValue(), map); + } + ArrayList photos = (ArrayList) map.get("Photos"); + if (photos == null) { + photos = new ArrayList(); + map.put("Photos", photos); + } + photos.add((Integer) childEntity.getProperty(key).getValue()); + setLink(parentEntity, navigation, childEntity); + } else if (type.getName().equals("Flight") && navigation.equals("From")) { + Map map = this.flightLinks.get(parentEntity.getProperty(key).getValue()); + if (map == null) { + map = new HashMap(); + this.flightLinks.put((Integer) parentEntity.getProperty(key).getValue(), map); + } + map.put("From", childEntity.getProperty(key).getValue()); + setLink(parentEntity, navigation, childEntity); + } else if (type.getName().equals("Flight") && navigation.equals("To")) { + Map map = this.flightLinks.get(parentEntity.getProperty(key).getValue()); + if (map == null) { + map = new HashMap(); + this.flightLinks.put((Integer) parentEntity.getProperty(key).getValue(), map); + } + map.put("To", childEntity.getProperty(key).getValue()); + setLink(parentEntity, navigation, childEntity); + } else if (type.getName().equals("Flight") && navigation.equals("Airline")) { + Map map = this.flightLinks.get(parentEntity.getProperty(key).getValue()); + if (map == null) { + map = new HashMap(); + this.flightLinks.put((Integer) parentEntity.getProperty(key).getValue(), map); + } + map.put("Airline", childEntity.getProperty(key).getValue()); + setLink(parentEntity, navigation, childEntity); + } else { + throw new RuntimeException("unknown relation"); + } + } + + protected static void setLink(Entity entity, final String navigationPropertyName, + final Entity target) { + Link link = new LinkImpl(); + link.setTitle(navigationPropertyName); + link.setInlineEntity(target); + entity.getNavigationLinks().add(link); + } + + public boolean updateNavigationLink(String navigationProperty, Entity parentEntity, + Entity updateEntity) { + boolean updated = false; + EdmEntityType type = this.metadata.getEdm().getEntityType( + new FullQualifiedName(parentEntity.getType())); + String key = type.getKeyPredicateNames().get(0); + + EdmEntityType updateType = this.metadata.getEdm().getEntityType( + new FullQualifiedName(updateEntity.getType())); + String updateKey = updateType.getKeyPredicateNames().get(0); + + if (type.getName().equals("Person") && navigationProperty.equals("Photo")) { + Map map = this.peopleLinks.get(parentEntity.getProperty(key).getValue()); + if (map != null) { + map.put("Photo", ((Long) updateEntity.getProperty(updateKey).getValue()).intValue()); + updated = true; + } + } else if (type.getName().equals("Flight") && navigationProperty.equals("From")) { + Map map = this.flightLinks.get(parentEntity.getProperty(key).getValue()); + if (map != null) { + map.put("From", updateEntity.getProperty(updateKey).getValue()); + updated = true; + } + } else if (type.getName().equals("Flight") && navigationProperty.equals("To")) { + Map map = this.flightLinks.get(parentEntity.getProperty(key).getValue()); + if (map != null) { + map.put("To", updateEntity.getProperty(updateKey).getValue()); + updated = true; + } + } else if (type.getName().equals("Flight") && navigationProperty.equals("Airline")) { + Map map = this.flightLinks.get(parentEntity.getProperty(key).getValue()); + if (map != null) { + map.put("Airline", updateEntity.getProperty(updateKey).getValue()); + updated = true; + } + } else { + throw new RuntimeException("unknown relation"); + } + return updated; + } + + public Entity createEntity(String entitySetName, Entity entity, String location) + throws ODataApplicationException { + + EntitySet set = this.entitySetMap.get(entitySetName); + EntityImpl copy = new EntityImpl(); + copy.setType(entity.getType()); + for (Property p : entity.getProperties()) { + copy.addProperty(p); + } + + try { + copy.setId(new URI(location)); + copy.setETag(UUID.randomUUID().toString()); + } catch (URISyntaxException e) { + throw new ODataApplicationException("Failed to create ID for entity", 500, + Locale.getDefault()); + } + + set.getEntities().add(copy); + return copy; + } + + public boolean deleteEntity(String entitySetName, String eTag, String key, Object keyValue) { + EntitySet set = getEntitySet(entitySetName); + Iterator it = set.getEntities().iterator(); + boolean removed = false; + while (it.hasNext()) { + Entity entity = it.next(); + if (entity.getProperty(key).getValue().equals(keyValue) && eTag.equals("*") + || eTag.equals(entity.getETag())) { + it.remove(); + removed = true; + break; + } + } + return removed; + } + + public boolean updateProperty(String entitySetName, String eTag, String key, Object keyValue, + Property property) { + EntitySet set = getEntitySet(entitySetName); + Iterator it = set.getEntities().iterator(); + boolean replaced = false; + while (it.hasNext()) { + Entity entity = it.next(); + if (entity.getProperty(key).getValue().equals(keyValue) && eTag.equals("*") + || eTag.equals(entity.getETag())) { + entity.getProperty(property.getName()).setValue(property.getValueType(), + property.getValue()); + replaced = true; + break; + } + } + return replaced; + } + + public EntitySet getNavigableEntitySet(Entity parentEntity, UriResourceNavigation navigation) { + EdmEntityType type = this.metadata.getEdm().getEntityType( + new FullQualifiedName(parentEntity.getType())); + + String key = type.getKeyPredicateNames().get(0); + String linkName = navigation.getProperty().getName(); + + EntitySet results = null; + if (type.getName().equals("Person") && linkName.equals("Friends")) { + results = getFriends((String) parentEntity.getProperty(key).getValue()); + } else if (type.getName().equals("Person") && linkName.equals("Trips")) { + results = getTrips((String) parentEntity.getProperty(key).getValue()); + } else if (type.getName().equals("Trip") && linkName.equals("PlanItems")) { + EntitySetImpl planitems = new EntitySetImpl(); + if (navigation.getTypeFilterOnCollection() == null) { + results = getPlanItems((Integer) parentEntity.getProperty(key).getValue(), planitems); + } else if (navigation.getTypeFilterOnCollection().getName().equals("Flight")) { + results = getFlights((Integer) parentEntity.getProperty(key).getValue(), planitems); + } else if (navigation.getTypeFilterOnCollection().getName().equals("Event")) { + results = getEvents((Integer) parentEntity.getProperty(key).getValue(), planitems); + } else { + throw new RuntimeException("unknown relation"); + } + } else if (type.getName().equals("Trip") && linkName.equals("Photos")) { + results = getTripPhotos((Integer) parentEntity.getProperty(key).getValue()); + } + return results; + } + + public Entity getNavigableEntity(Entity parentEntity, UriResourceNavigation navigation) + throws ODataApplicationException { + EdmEntityType type = this.metadata.getEdm().getEntityType( + new FullQualifiedName(parentEntity.getType())); + + String key = type.getKeyPredicateNames().get(0); + String linkName = navigation.getProperty().getName(); + + EntitySet results = null; + if (navigation.getProperty().isCollection()) { + results = getNavigableEntitySet(parentEntity, navigation); + return this.getEntity(results, navigation.getKeyPredicates()); + } + if (type.getName().equals("Person") && linkName.equals("Photo")) { + return getPhoto((String) parentEntity.getProperty(key).getValue()); + } else if (type.getName().equals("Flight") && linkName.equals("From")) { + return getFlightFrom((Integer) parentEntity.getProperty(key).getValue()); + } else if (type.getName().equals("Flight") && linkName.equals("To")) { + return getFlightTo((Integer) parentEntity.getProperty(key).getValue()); + } else if (type.getName().equals("Flight") && linkName.equals("Airline")) { + return getFlightAirline((Integer) parentEntity.getProperty(key).getValue()); + } else { + throw new RuntimeException("unknown relation"); + } + } + + public boolean removeNavigationLink(String navigationProperty, Entity parentEntity, + Entity deleteEntity) { + boolean removed = false; + EdmEntityType type = this.metadata.getEdm().getEntityType( + new FullQualifiedName(parentEntity.getType())); + String key = type.getKeyPredicateNames().get(0); + + if (type.getName().equals("Person") && navigationProperty.equals("Friends")) { + Map map = this.peopleLinks.get(parentEntity.getProperty(key).getValue()); + if (map != null) { + ArrayList friends = (ArrayList) map.get("Friends"); + if (friends != null) { + friends.remove(deleteEntity.getProperty(key).getValue()); + removed = true; + } + } + } else if (type.getName().equals("Person") && navigationProperty.equals("Trips")) { + Map map = this.peopleLinks.get(parentEntity.getProperty(key).getValue()); + if (map != null) { + ArrayList trips = (ArrayList) map.get("Trips"); + if (trips != null) { + trips.remove(deleteEntity.getProperty(key).getValue()); + removed = true; + } + } + } else if (type.getName().equals("Person") && navigationProperty.equals("Photo")) { + Map map = this.peopleLinks.get(parentEntity.getProperty(key).getValue()); + if (map != null) { + map.remove("Photo"); + removed = true; + } + } else if (type.getName().equals("Trip") && navigationProperty.equals("PlanItems")) { + Map map = this.tripLinks.get(parentEntity.getProperty(key).getValue()); + if (map != null) { + if (deleteEntity.getType().equals("Flight")) { + ArrayList flights = (ArrayList) map.get("Flights"); + if (flights != null) { + flights.remove(deleteEntity.getProperty(key).getValue()); + removed = true; + } + } else { + ArrayList events = (ArrayList) map.get("Events"); + if (events != null) { + events.remove(deleteEntity.getProperty(key).getValue()); + removed = true; + } + } + } + } else if (type.getName().equals("Trip") && navigationProperty.equals("Photo")) { + Map map = this.tripLinks.get(parentEntity.getProperty(key).getValue()); + if (map != null) { + ArrayList photos = (ArrayList) map.get("Photos"); + if (photos != null) { + photos.remove(deleteEntity.getProperty(key).getValue()); + removed = true; + } + } + } else if (type.getName().equals("Flight") && navigationProperty.equals("From")) { + Map map = this.flightLinks.get(parentEntity.getProperty(key).getValue()); + if (map != null) { + map.remove("From"); + removed = true; + } + } else if (type.getName().equals("Flight") && navigationProperty.equals("To")) { + Map map = this.flightLinks.get(parentEntity.getProperty(key).getValue()); + if (map != null) { + map.remove("To"); + removed = true; + } + } else if (type.getName().equals("Flight") && navigationProperty.equals("Airline")) { + Map map = this.flightLinks.get(parentEntity.getProperty(key).getValue()); + if (map != null) { + map.remove("Airline"); + removed = true; + } + } else { + throw new RuntimeException("unknown relation"); + } + return removed; + } + + // note these are not tied to entities for simplicity sake + public boolean updateMedia(Entity entity, InputStream mediaContent) + throws ODataApplicationException { + checkForMedia(entity); + return true; + } + + // note these are not tied to entities for simplicity sake + public InputStream readMedia(Entity entity) throws ODataApplicationException { + checkForMedia(entity); + try { + return new FileInputStream(new File("src/test/resources/OlingoOrangeTM.png")); + } catch (FileNotFoundException e) { + throw new ODataApplicationException("image not found", 500, Locale.getDefault()); + } + } + + // note these are not tied to entities for simplicity sake + public boolean deleteMedia(Entity entity) throws ODataApplicationException { + checkForMedia(entity); + return true; + } + + private void checkForMedia(Entity entity) throws ODataApplicationException { + EdmEntityType type = this.metadata.getEdm().getEntityType( + new FullQualifiedName(entity.getType())); + if (!type.hasStream()) { + throw new ODataApplicationException("No Media proeprty on the entity", 500, + Locale.getDefault()); + } + } + + public boolean deleteStream(Entity entity, EdmProperty property) { + // should remove stream links + return true; + } + + public boolean updateStream(Entity entity, EdmProperty property, InputStream streamContent) { + // should add stream links + return true; + } +} \ No newline at end of file diff --git a/lib/server-core-ext/src/test/java/org/apache/olingo/server/example/TripPinHandler.java b/lib/server-core-ext/src/test/java/org/apache/olingo/server/example/TripPinHandler.java new file mode 100644 index 000000000..19a038761 --- /dev/null +++ b/lib/server-core-ext/src/test/java/org/apache/olingo/server/example/TripPinHandler.java @@ -0,0 +1,546 @@ +/* + * 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.server.example; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; +import java.util.Locale; +import java.util.Random; + +import org.apache.olingo.commons.api.data.Entity; +import org.apache.olingo.commons.api.data.EntitySet; +import org.apache.olingo.commons.api.data.Link; +import org.apache.olingo.commons.api.data.Property; +import org.apache.olingo.commons.api.edm.EdmAction; +import org.apache.olingo.commons.api.edm.EdmEntitySet; +import org.apache.olingo.commons.api.edm.EdmEntityType; +import org.apache.olingo.commons.api.edm.EdmFunction; +import org.apache.olingo.commons.api.edm.EdmProperty; +import org.apache.olingo.commons.api.edm.EdmSingleton; +import org.apache.olingo.commons.api.format.ContentType; +import org.apache.olingo.commons.api.http.HttpMethod; +import org.apache.olingo.commons.core.data.EntitySetImpl; +import org.apache.olingo.server.api.OData; +import org.apache.olingo.server.api.ODataApplicationException; +import org.apache.olingo.server.api.ODataRequest; +import org.apache.olingo.server.api.ODataResponse; +import org.apache.olingo.server.api.ODataTranslatedException; +import org.apache.olingo.server.api.ServiceMetadata; +import org.apache.olingo.server.api.uri.UriParameter; +import org.apache.olingo.server.api.uri.UriResourceNavigation; +import org.apache.olingo.server.core.ServiceHandler; +import org.apache.olingo.server.core.requests.ActionRequest; +import org.apache.olingo.server.core.requests.DataRequest; +import org.apache.olingo.server.core.requests.FunctionRequest; +import org.apache.olingo.server.core.requests.MediaRequest; +import org.apache.olingo.server.core.requests.MetadataRequest; +import org.apache.olingo.server.core.requests.ServiceDocumentRequest; +import org.apache.olingo.server.core.responses.CountResponse; +import org.apache.olingo.server.core.responses.EntityResponse; +import org.apache.olingo.server.core.responses.EntitySetResponse; +import org.apache.olingo.server.core.responses.MetadataResponse; +import org.apache.olingo.server.core.responses.NoContentResponse; +import org.apache.olingo.server.core.responses.PrimitiveValueResponse; +import org.apache.olingo.server.core.responses.PropertyResponse; +import org.apache.olingo.server.core.responses.ServiceDocumentResponse; +import org.apache.olingo.server.core.responses.ServiceResponse; +import org.apache.olingo.server.core.responses.ServiceResponseVisior; +import org.apache.olingo.server.core.responses.StreamResponse; + +public class TripPinHandler implements ServiceHandler { + private OData odata; + private ServiceMetadata serviceMetadata; + private final TripPinDataModel dataModel; + + public TripPinHandler(TripPinDataModel datamodel) { + this.dataModel = datamodel; + } + + @Override + public void init(OData odata, ServiceMetadata serviceMetadata) { + this.odata = odata; + this.serviceMetadata = serviceMetadata; + } + + @Override + public void readMetadata(MetadataRequest request, MetadataResponse response) + throws ODataTranslatedException, ODataApplicationException { + response.writeMetadata(); + } + + @Override + public void readServiceDocument(ServiceDocumentRequest request, ServiceDocumentResponse response) + throws ODataTranslatedException, ODataApplicationException { + response.writeServiceDocument(request.getODataRequest().getRawBaseUri()); + } + + static class EntityDetails { + EntitySet entitySet = null; + Entity entity = null; + EdmEntityType entityType; + String navigationProperty; + Entity parentEntity = null; + } + + private EntityDetails process(final DataRequest request) throws ODataApplicationException { + EntitySet entitySet = null; + Entity entity = null; + EdmEntityType entityType; + Entity parentEntity = null; + + if (request.isSingleton()) { + EdmSingleton singleton = request.getUriResourceSingleton().getSingleton(); + entityType = singleton.getEntityType(); + if (singleton.getName().equals("Me")) { + entitySet = this.dataModel.getEntitySet("People"); + entity = entitySet.getEntities().get(0); + } + } else { + final EdmEntitySet edmEntitySet = request.getEntitySet(); + entityType = edmEntitySet.getEntityType(); + List keys = request.getKeyPredicates(); + + // TODO: This example so far ignores all system options; but a real + // service should not + if (keys != null && !keys.isEmpty()) { + entity = this.dataModel.getEntity(edmEntitySet.getName(), keys); + } else { + int skip = 0; + if (request.getUriInfo().getSkipTokenOption() != null) { + skip = Integer.parseInt(request.getUriInfo().getSkipTokenOption().getValue()); + } + int pageSize = getPageSize(request); + entitySet = this.dataModel.getEntitySet(edmEntitySet.getName(), skip, pageSize); + if (entitySet.getEntities().size() == pageSize) { + try { + entitySet.setNext(new URI(request.getODataRequest().getRawRequestUri() + "?$skiptoken=" + + (skip + pageSize))); + } catch (URISyntaxException e) { + throw new ODataApplicationException(e.getMessage(), 500, Locale.getDefault()); + } + } + } + } + EntityDetails details = new EntityDetails(); + + if (!request.getNavigations().isEmpty() && entity != null) { + UriResourceNavigation lastNavigation = request.getNavigations().getLast(); + for (UriResourceNavigation nav : request.getNavigations()) { + entityType = nav.getProperty().getType(); + if (nav.isCollection()) { + entitySet = this.dataModel.getNavigableEntitySet(entity, nav); + } else { + parentEntity = entity; + entity = this.dataModel.getNavigableEntity(parentEntity, nav); + } + } + details.navigationProperty = lastNavigation.getProperty().getName(); + } + + details.entity = entity; + details.entitySet = entitySet; + details.entityType = entityType; + details.parentEntity = parentEntity; + return details; + } + + @Override + public void read(final DataRequest request, final T response) + throws ODataTranslatedException, ODataApplicationException { + + final EntityDetails details = process(request); + + response.accepts(new ServiceResponseVisior() { + @Override + public void visit(CountResponse response) throws ODataTranslatedException, ODataApplicationException { + response.writeCount(details.entitySet.getCount()); + } + + @Override + public void visit(PrimitiveValueResponse response) throws ODataTranslatedException, + ODataApplicationException { + EdmProperty edmProperty = request.getUriResourceProperty().getProperty(); + Property property = details.entity.getProperty(edmProperty.getName()); + response.write(property.getValue()); + } + + @Override + public void visit(PropertyResponse response) throws ODataTranslatedException, + ODataApplicationException { + EdmProperty edmProperty = request.getUriResourceProperty().getProperty(); + Property property = details.entity.getProperty(edmProperty.getName()); + response.writeProperty(edmProperty.getType(), property); + } + + @Override + public void visit(StreamResponse response) throws ODataTranslatedException, + ODataApplicationException { + // stream property response + response.writeStreamResponse(new ByteArrayInputStream("dummy".getBytes()), + ContentType.APPLICATION_OCTET_STREAM); + } + + @Override + public void visit(EntitySetResponse response) throws ODataTranslatedException, + ODataApplicationException { + if (request.getPreference("odata.maxpagesize") != null) { + response.writeHeader("Preference-Applied", request.getPreference("odata.maxpagesize")); + } + if (details.entity == null && !request.getNavigations().isEmpty()) { + response.writeReadEntitySet(details.entityType, new EntitySetImpl()); + } else { + response.writeReadEntitySet(details.entityType, details.entitySet); + } + } + + @Override + public void visit(EntityResponse response) throws ODataTranslatedException, + ODataApplicationException { + if (details.entity == null && !request.getNavigations().isEmpty()) { + response.writeNoContent(true); + } else { + response.writeReadEntity(details.entityType, details.entity); + } + } + }); + } + + private int getPageSize(DataRequest request) { + String size = request.getPreference("odata.maxpagesize"); + if (size == null) { + return 8; + } + return Integer.parseInt(size); + } + + @Override + public void createEntity(DataRequest request, Entity entity, EntityResponse response) + throws ODataTranslatedException, ODataApplicationException { + EdmEntitySet edmEntitySet = request.getEntitySet(); + + String location = buildLocation(entity, edmEntitySet.getName(), edmEntitySet.getEntityType()); + Entity created = this.dataModel.createEntity(edmEntitySet.getName(), entity, location); + + try { + // create references, they come in "@odata.bind" value + List bindings = entity.getNavigationBindings(); + if (bindings != null & !bindings.isEmpty()) { + for (Link link : bindings) { + String navigationProperty = link.getTitle(); + String uri = link.getBindingLink(); + if (uri != null) { + DataRequest bindingRequest = request.parseLink(new URI(uri)); + + Entity reference = this.dataModel.getEntity(bindingRequest.getEntitySet().getName(), + bindingRequest.getKeyPredicates()); + + this.dataModel.addNavigationLink(navigationProperty, created, reference); + + } else { + for (String binding : link.getBindingLinks()) { + DataRequest bindingRequest = request.parseLink(new URI(binding)); + + Entity reference = this.dataModel.getEntity(bindingRequest.getEntitySet().getName(), + bindingRequest.getKeyPredicates()); + + this.dataModel.addNavigationLink(navigationProperty, created, reference); + } + } + } + } + } catch (URISyntaxException e) { + throw new ODataApplicationException(e.getMessage(), 500, Locale.getDefault()); + } + + response.writeCreatedEntity(edmEntitySet.getEntityType(), created, location); + } + + static String buildLocation(Entity entity, String name, EdmEntityType type) { + String location = "/" + name + "("; + int i = 0; + boolean usename = type.getKeyPredicateNames().size() > 1; + + for (String key : type.getKeyPredicateNames()) { + if (i > 0) { + location += ","; + } + i++; + if (usename) { + location += (key + "="); + } + if (entity.getProperty(key).getType().equals("Edm.String")) { + location = location + "'" + entity.getProperty(key).getValue().toString() + "'"; + } else { + location = location + entity.getProperty(key).getValue().toString(); + } + } + location += ")"; + return location; + } + + @Override + public void updateEntity(DataRequest request, Entity entity, boolean merge, String entityETag, + EntityResponse response) throws ODataTranslatedException, ODataApplicationException { + response.writeServerError(true); + } + + @Override + public void deleteEntity(DataRequest request, String eTag, EntityResponse response) + throws ODataTranslatedException, ODataApplicationException { + + EdmEntitySet edmEntitySet = request.getEntitySet(); + Entity entity = this.dataModel.getEntity(edmEntitySet.getName(), request.getKeyPredicates()); + if (entity == null) { + response.writeNotFound(true); + return; + } + String key = edmEntitySet.getEntityType().getKeyPredicateNames().get(0); + boolean removed = this.dataModel.deleteEntity(edmEntitySet.getName(), eTag, key, entity + .getProperty(key).getValue()); + + if (removed) { + response.writeDeletedEntityOrReference(); + } else { + response.writeNotFound(true); + } + } + + @Override + public void updateProperty(DataRequest request, final Property property, boolean merge, + String entityETag, PropertyResponse response) throws ODataTranslatedException, + ODataApplicationException { + + EdmEntitySet edmEntitySet = request.getEntitySet(); + Entity entity = this.dataModel.getEntity(edmEntitySet.getName(), request.getKeyPredicates()); + if (entity == null) { + response.writeNotFound(true); + return; + } + + String key = edmEntitySet.getEntityType().getKeyPredicateNames().get(0); + boolean replaced = this.dataModel.updateProperty(edmEntitySet.getName(), entityETag, key, + entity.getProperty(key).getValue(), property); + + if (replaced) { + if (property.getValue() == null) { + response.writePropertyDeleted(); + } else { + response.writePropertyUpdated(); + } + } else { + response.writeServerError(true); + } + } + + @Override + public void invoke(FunctionRequest request, HttpMethod method, + T response) throws ODataTranslatedException, ODataApplicationException { + EdmFunction function = request.getFunction(); + if (function.getName().equals("GetNearestAirport")) { + + final EdmEntityType type = serviceMetadata.getEdm().getEntityContainer(null) + .getEntitySet("Airports").getEntityType(); + + EntitySet es = this.dataModel.getEntitySet("Airports"); + int i = new Random().nextInt(es.getEntities().size()); + final Entity entity = es.getEntities().get(i); + + response.accepts(new ServiceResponseVisior() { + @Override + public void visit(EntityResponse response) throws ODataTranslatedException, + ODataApplicationException { + response.writeReadEntity(type, entity); + } + }); + } + } + + @Override + public void invoke(ActionRequest request, String eTag, T response) + throws ODataTranslatedException, ODataApplicationException { + EdmAction action = request.getAction(); + if (action.getName().equals("ResetDataSource")) { + try { + this.dataModel.loadData(); + response.accepts(new ServiceResponseVisior() { + @Override + public void visit(NoContentResponse response) throws ODataTranslatedException, + ODataApplicationException { + response.writeNoContent(); + } + }); + } catch (Exception e) { + response.writeServerError(true); + } + } else { + response.writeServerError(true); + } + } + + @Override + public void readMediaStream(MediaRequest request, StreamResponse response) + throws ODataTranslatedException, ODataApplicationException { + + final EdmEntitySet edmEntitySet = request.getEntitySet(); + List keys = request.getKeyPredicates(); + Entity entity = this.dataModel.getEntity(edmEntitySet.getName(), keys); + + InputStream contents = this.dataModel.readMedia(entity); + response.writeStreamResponse(contents, request.getResponseContentType()); + } + + @Override + public void upsertMediaStream(MediaRequest request, String entityETag, InputStream mediaContent, + NoContentResponse response) throws ODataTranslatedException, ODataApplicationException { + final EdmEntitySet edmEntitySet = request.getEntitySet(); + List keys = request.getKeyPredicates(); + Entity entity = this.dataModel.getEntity(edmEntitySet.getName(), keys); + + if (mediaContent == null) { + boolean deleted = this.dataModel.deleteMedia(entity); + if (deleted) { + response.writeNoContent(); + } else { + response.writeNotFound(); + } + } else { + boolean updated = this.dataModel.updateMedia(entity, mediaContent); + if (updated) { + response.writeNoContent(); + } else { + response.writeServerError(true); + } + } + } + + @Override + public void upsertStreamProperty(DataRequest request, String entityETag, InputStream streamContent, + NoContentResponse response) throws ODataTranslatedException, ODataApplicationException { + final EdmEntitySet edmEntitySet = request.getEntitySet(); + List keys = request.getKeyPredicates(); + Entity entity = this.dataModel.getEntity(edmEntitySet.getName(), keys); + + EdmProperty property = request.getUriResourceProperty().getProperty(); + + if (streamContent == null) { + boolean deleted = this.dataModel.deleteStream(entity, property); + if (deleted) { + response.writeNoContent(); + } else { + response.writeNotFound(); + } + } else { + boolean updated = this.dataModel.updateStream(entity, property, streamContent); + if (updated) { + response.writeNoContent(); + } else { + response.writeServerError(true); + } + } + } + + @Override + public void addReference(DataRequest request, String entityETag, List references, + NoContentResponse response) throws ODataTranslatedException, ODataApplicationException { + + final EntityDetails details = process(request); + + for (URI reference : references) { + DataRequest bindingRequest = request.parseLink(reference); + Entity linkEntity = this.dataModel.getEntity(bindingRequest.getEntitySet().getName(), + bindingRequest.getKeyPredicates()); + this.dataModel.addNavigationLink(details.navigationProperty, details.entity, linkEntity); + } + response.writeNoContent(); + } + + @Override + public void updateReference(DataRequest request, String entityETag, URI updateId, + NoContentResponse response) throws ODataTranslatedException, ODataApplicationException { + // this single valued navigation. + final EntityDetails details = process(request); + DataRequest updateRequest = request.parseLink(updateId); + Entity updateEntity = this.dataModel.getEntity(updateRequest.getEntitySet().getName(), + updateRequest.getKeyPredicates()); + boolean updated = false; + if (updateEntity != null) { + updated = this.dataModel.updateNavigationLink(details.navigationProperty, + details.parentEntity, updateEntity); + } + + if (updated) { + response.writeNoContent(); + } else { + response.writeServerError(true); + } + } + + @Override + public void deleteReference(DataRequest request, URI deleteId, String entityETag, + NoContentResponse response) throws ODataTranslatedException, ODataApplicationException { + boolean removed = false; + if (deleteId != null) { + final EntityDetails details = process(request); + DataRequest deleteRequest = request.parseLink(deleteId); + Entity deleteEntity = this.dataModel.getEntity(deleteRequest.getEntitySet().getName(), + deleteRequest.getKeyPredicates()); + if (deleteEntity != null) { + removed = this.dataModel.removeNavigationLink(details.navigationProperty, details.entity, + deleteEntity); + } + } else { + // this single valued navigation. + final EntityDetails details = process(request); + removed = this.dataModel.removeNavigationLink(details.navigationProperty, + details.parentEntity, details.entity); + } + if (removed) { + response.writeNoContent(); + } else { + response.writeServerError(true); + } + } + + @Override + public void anyUnsupported(ODataRequest request, ODataResponse response) + throws ODataTranslatedException, ODataApplicationException { + response.setStatusCode(500); + } + + @Override + public String startTransaction() { + return null; + } + + @Override + public void commit(String txnId) { + } + + @Override + public void rollback(String txnId) { + } + + @Override + public void crossJoin(DataRequest dataRequest, List entitySetNames, ODataResponse response) { + response.setStatusCode(200); + } +} diff --git a/lib/server-core-ext/src/test/java/org/apache/olingo/server/example/TripPinServiceTest.java b/lib/server-core-ext/src/test/java/org/apache/olingo/server/example/TripPinServiceTest.java new file mode 100644 index 000000000..4b26b8eb5 --- /dev/null +++ b/lib/server-core-ext/src/test/java/org/apache/olingo/server/example/TripPinServiceTest.java @@ -0,0 +1,756 @@ +/* + * 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.server.example; + +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 static org.junit.Assert.fail; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Iterator; + +import org.apache.olingo.commons.core.Encoder; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentProvider; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Test; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; + +/** + * Please note that NONE of the system query options are developed in the sample + * service like $filter, $orderby etc. So using those options will be ignored + * right now. These tests designed to test the framework, all options are responsibilities + * of service developer. + */ +public class TripPinServiceTest { + private static Server server = new Server(); + private static String baseURL; + private static HttpClient http = new HttpClient(); + + @BeforeClass + public static void beforeTest() throws Exception { + ServerConnector connector = new ServerConnector(server); + server.setConnectors(new Connector[] { connector }); + + ServletContextHandler context = new ServletContextHandler(); + context.setContextPath("/trippin"); + context.addServlet(new ServletHolder(new TripPinServlet()), "/*"); + server.setHandler(context); + server.start(); + int port = connector.getLocalPort(); + http.start(); + baseURL = "http://localhost:"+port+"/trippin"; + } + + @AfterClass + public static void afterTest() throws Exception { + server.stop(); + } + + @Test + public void testEntitySet() throws Exception { + ContentResponse response = http.newRequest(baseURL + "/People") + .header("Content-Type", "application/json;odata.metadata=minimal") + .method(HttpMethod.GET) + .send(); + assertEquals(200, response.getStatus()); + + JsonNode node = getJSONNode(response); + + assertEquals("$metadata#People", node.get("@odata.context").asText()); + assertEquals(baseURL+"/People?$skiptoken=8", node.get("@odata.nextLink").asText()); + + JsonNode person = ((ArrayNode)node.get("value")).get(0); + assertEquals("russellwhyte", person.get("UserName").asText()); + } + + private JsonNode getJSONNode(ContentResponse response) throws IOException, + JsonProcessingException { + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode node = objectMapper.readTree(response.getContent()); + return node; + } + + @Test + public void testReadEntitySetWithPaging() throws Exception { + ContentResponse response = http.newRequest(baseURL + "/People") + .header("Prefer", "odata.maxpagesize=10").send(); + + assertEquals(200, response.getStatus()); + + JsonNode node = getJSONNode(response); + assertEquals("$metadata#People", node.get("@odata.context").asText()); + assertEquals(baseURL+"/People?$skiptoken=10", node.get("@odata.nextLink").asText()); + + JsonNode person = ((ArrayNode)node.get("value")).get(0); + assertEquals("russellwhyte", person.get("UserName").asText()); + + assertNotNull(response.getHeaders().get("Preference-Applied")); + } + + @Test + public void testReadEntityWithKey() throws Exception { + ContentResponse response = http.GET(baseURL + "/Airlines('AA')"); + assertEquals(200, response.getStatus()); + JsonNode node = getJSONNode(response); + assertEquals("$metadata#Airlines/$entity", node.get("@odata.context").asText()); + assertEquals("American Airlines", node.get("Name").asText()); + } + + @Test + public void testReadEntityWithNonExistingKey() throws Exception { + ContentResponse response = http.GET(baseURL + "/Airlines('OO')"); + assertEquals(404, response.getStatus()); + } + + @Test + public void testRead$Count() throws Exception { + ContentResponse response = http.GET(baseURL + "/Airlines/$count"); + assertEquals(200, response.getStatus()); + assertEquals("15", response.getContentAsString()); + } + + @Test + public void testReadPrimitiveProperty() throws Exception { + ContentResponse response = http.GET(baseURL + "/Airlines('AA')/Name"); + assertEquals(200, response.getStatus()); + JsonNode node = getJSONNode(response); + assertEquals("$metadata#Airlines('AA')/Name", node.get("@odata.context").asText()); + assertEquals("American Airlines", node.get("value").asText()); + } + + @Test + public void testReadNonExistentProperty() throws Exception { + ContentResponse response = http.GET(baseURL + "/Airlines('AA')/Unknown"); + assertEquals(404, response.getStatus()); + } + + @Test + public void testReadPrimitiveArrayProperty() throws Exception { + ContentResponse response = http.GET(baseURL + "/People('russellwhyte')/Emails"); + assertEquals(200, response.getStatus()); + JsonNode node = getJSONNode(response); + assertEquals("$metadata#People('russellwhyte')/Emails", node.get("@odata.context").asText()); + assertTrue(node.get("value").isArray()); + assertEquals("Russell@example.com", ((ArrayNode)node.get("value")).get(0).asText()); + assertEquals("Russell@contoso.com", ((ArrayNode)node.get("value")).get(1).asText()); + } + + @Test + public void testReadPrimitivePropertyValue() throws Exception { + ContentResponse response = http.GET(baseURL + "/Airlines('AA')/Name/$value"); + assertEquals(200, response.getStatus()); + assertEquals("American Airlines", response.getContentAsString()); + } + + @Test @Ignore + // TODO: Support geometry types to make this run + public void testReadComplexProperty() throws Exception { + ContentResponse response = http.GET(baseURL + "/Airports('KSFO')/Location"); + fail("support geometry type"); + } + + @Test + public void testReadComplexArrayProperty() throws Exception { + ContentResponse response = http.GET(baseURL + "/People('russellwhyte')/AddressInfo"); + assertEquals(200, response.getStatus()); + JsonNode node = getJSONNode(response); + assertEquals("$metadata#People('russellwhyte')/AddressInfo", node.get("@odata.context").asText()); + assertTrue(node.get("value").isArray()); + assertEquals("187 Suffolk Ln.", ((ArrayNode)node.get("value")).get(0).get("Address").asText()); + } + + @Test + public void testReadMedia() throws Exception { + ContentResponse response = http.GET(baseURL + "/Photos(1)/$value"); + assertEquals(200, response.getStatus()); + } + + @Test + public void testCreateMedia() throws Exception { + // treating update and create as same for now, as there is details about + // how entity payload and media payload can be sent at same time in request's body + String editUrl = baseURL + "/Photos(1)/$value"; + ContentResponse response = http.newRequest(editUrl) + .content(content("bytecontents"), "image/jpeg") + .method(HttpMethod.PUT) + .send(); + assertEquals(204, response.getStatus()); + } + + @Test + public void testDeleteMedia() throws Exception { + // treating update and create as same for now, as there is details about + // how entity payload and media payload can be sent at same time in request's body + String editUrl = baseURL + "/Photos(1)/$value"; + ContentResponse response = http.newRequest(editUrl) + .content(content("bytecontents"), "image/jpeg") + .method(HttpMethod.DELETE) + .send(); + assertEquals(204, response.getStatus()); + } + + @Test + public void testCreateStream() throws Exception { + // treating update and create as same for now, as there is details about + // how entity payload and media payload can be sent at same time in request's body + String editUrl = baseURL + "/Airlines('AA')/Picture"; + ContentResponse response = http.newRequest(editUrl) + .content(content("bytecontents"), "image/jpeg") + .method(HttpMethod.POST) + .send(); + // method not allowed + assertEquals(405, response.getStatus()); + } + + @Test + public void testCreateStream2() throws Exception { + // treating update and create as same for now, as there is details about + // how entity payload and media payload can be sent at same time in request's body + String editUrl = baseURL + "/Airlines('AA')/Picture"; + ContentResponse response = http.newRequest(editUrl) + .content(content("bytecontents"), "image/jpeg") + .method(HttpMethod.PUT) + .send(); + assertEquals(204, response.getStatus()); + } + + @Test + public void testDeleteStream() throws Exception { + // treating update and create as same for now, as there is details about + // how entity payload and media payload can be sent at same time in request's body + String editUrl = baseURL + "/Airlines('AA')/Picture"; + ContentResponse response = http.newRequest(editUrl) + .method(HttpMethod.DELETE) + .send(); + assertEquals(204, response.getStatus()); + } + + @Test + public void testReadStream() throws Exception { + // treating update and create as same for now, as there is details about + // how entity payload and media payload can be sent at same time in request's body + String editUrl = baseURL + "/Airlines('AA')/Picture"; + ContentResponse response = http.newRequest(editUrl) + .method(HttpMethod.GET) + .send(); + assertEquals(200, response.getStatus()); + } + + @Test + public void testLambdaAny() throws Exception { + // this is just testing to see the labba expresions are going through the + // framework, none of the system options are not implemented in example service + String query = "Friends/any(d:d/UserName eq 'foo')"; + ContentResponse response = http.newRequest(baseURL + "/People?$filter="+Encoder.encode(query)) + .method(HttpMethod.GET) + .send(); + assertEquals(200, response.getStatus()); + } + + @Test + public void testSingleton() throws Exception { + ContentResponse response = http.GET(baseURL + "/Me"); + assertEquals(200, response.getStatus()); + JsonNode node = getJSONNode(response); + assertEquals("$metadata#Me", node.get("@odata.context").asText()); + assertEquals("russellwhyte", node.get("UserName").asText()); + } + + @Test + public void testSelectOption() throws Exception { + ContentResponse response = http.GET(baseURL + "/People('russellwhyte')?$select=FirstName,LastName"); + assertEquals(200, response.getStatus()); + JsonNode node = getJSONNode(response); + assertEquals("$metadata#People(FirstName,LastName)/$entity", node.get("@odata.context").asText()); + assertEquals("Russell", node.get("FirstName").asText()); + } + + @Test + public void testActionImportWithNoResponse() throws Exception { + ContentResponse response = http.POST(baseURL + "/ResetDataSource").send(); + assertEquals(204, response.getStatus()); + } + + @Test + public void testFunctionImport() throws Exception { + //TODO: fails because of lack of geometery support + ContentResponse response = http.GET(baseURL + "/GetNearestAirport(lat=23.0,lon=34.0)"); + } + + @Test + public void testBadReferences() throws Exception { + ContentResponse response = http.GET(baseURL + "/People('russelwhyte')/$ref"); + assertEquals(405, response.getStatus()); + } + + @Test + public void testReadReferences() throws Exception { + ContentResponse response = http.GET(baseURL + "/People('russellwhyte')/Friends/$ref"); + assertEquals(200, response.getStatus()); + JsonNode node = getJSONNode(response); + assertEquals("$metadata#Collection($ref)", node.get("@odata.context").asText()); + assertTrue(node.get("value").isArray()); + assertEquals("/People('scottketchum')", ((ArrayNode)node.get("value")).get(0).get("@odata.id").asText()); + } + + @Test + public void testAddCollectionReferences() throws Exception { + //GET + ContentResponse response = http.GET(baseURL + "/People('kristakemp')/Friends/$ref"); + assertEquals(200, response.getStatus()); + JsonNode node = getJSONNode(response); + + assertTrue(node.get("value").isArray()); + assertEquals("/People('genevievereeves')", ((ArrayNode)node.get("value")).get(0).get("@odata.id").asText()); + assertNull(((ArrayNode)node.get("value")).get(1)); + + //ADD + String payload = "{\n" + + " \"@odata.context\": \""+baseURL+"/$metadata#Collection($ref)\",\n" + + " \"value\": [\n" + + " { \"@odata.id\": \"People('russellwhyte')\" },\n" + + " { \"@odata.id\": \"People('scottketchum')\" } \n" + + " ]\n" + + "}"; + response = http.POST(baseURL + "/People('kristakemp')/Friends/$ref") + .content(content(payload), "application/json") + .send(); + assertEquals(204, response.getStatus()); + + //GET + response = http.GET(baseURL + "/People('kristakemp')/Friends/$ref"); + assertEquals(200, response.getStatus()); + node = getJSONNode(response); + + assertTrue(node.get("value").isArray()); + assertEquals("/People('genevievereeves')", ((ArrayNode)node.get("value")).get(0).get("@odata.id").asText()); + assertEquals("/People('russellwhyte')", ((ArrayNode)node.get("value")).get(1).get("@odata.id").asText()); + assertEquals("/People('scottketchum')", ((ArrayNode)node.get("value")).get(2).get("@odata.id").asText()); + } + + + @Test + public void testEntityId() throws Exception { + ContentResponse response = http.GET(baseURL+"/$entity?$id="+baseURL + "/People('kristakemp')"); + assertEquals(200, response.getStatus()); + JsonNode node = getJSONNode(response); + assertEquals("$metadata#People/$entity", node.get("@odata.context").asText()); + assertEquals("kristakemp", node.get("UserName").asText()); + + // using relative URL + response = http.GET(baseURL+"/$entity?$id="+"People('kristakemp')"); + assertEquals(200, response.getStatus()); + node = getJSONNode(response); + assertEquals("$metadata#People/$entity", node.get("@odata.context").asText()); + assertEquals("kristakemp", node.get("UserName").asText()); + } + + @Test + public void testCreateReadDeleteEntity() throws Exception { + String payload = "{\n" + + " \"UserName\":\"olingodude\",\n" + + " \"FirstName\":\"Olingo\",\n" + + " \"LastName\":\"Apache\",\n" + + " \"Emails\":[\n" + + " \"olingo@apache.org\"\n" + + " ],\n" + + " \"AddressInfo\":[\n" + + " {\n" + + " \"Address\":\"100 apache Ln.\",\n" + + " \"City\":{\n" + + " \"CountryRegion\":\"United States\",\n" + + " \"Name\":\"Boise\",\n" + + " \"Region\":\"ID\"\n" + + " }\n" + + " }\n" + + " ],\n" + + " \"Gender\":\"0\",\n" + + " \"Concurrency\":635585295719432047\n" + + "}"; + ContentResponse response = http.POST(baseURL + "/People") + .content(content(payload), "application/json") + .header("Prefer", "return=minimal") + .send(); + // the below woud be 204, if minimal was not supplied + assertEquals(204, response.getStatus()); + assertEquals("/People('olingodude')", response.getHeaders().get("Location")); + assertEquals("return=minimal", response.getHeaders().get("Preference-Applied")); + + String location = baseURL+response.getHeaders().get("Location"); + response = http.GET(location); + assertEquals(200, response.getStatus()); + + response = http.newRequest(location).method(HttpMethod.DELETE).send(); + assertEquals(204, response.getStatus()); + + response = http.GET(location); + assertEquals(404, response.getStatus()); + } + + + @Test + public void testCreateEntityWithLinkToRelatedEntities() throws Exception { + String payload = "{\n" + + " \"UserName\":\"olingo\",\n" + + " \"FirstName\":\"Olingo\",\n" + + " \"LastName\":\"Apache\",\n" + + " \"Emails\":[\n" + + " \"olingo@apache.org\"\n" + + " ],\n" + + " \"AddressInfo\":[\n" + + " {\n" + + " \"Address\":\"100 apache Ln.\",\n" + + " \"City\":{\n" + + " \"CountryRegion\":\"United States\",\n" + + " \"Name\":\"Boise\",\n" + + " \"Region\":\"ID\"\n" + + " }\n" + + " }\n" + + " ],\n" + + " \"Gender\":\"0\",\n" + + " \"Concurrency\":635585295719432047,\n" + + "\"Friends@odata.bind\":[\"" + + baseURL+"/People('russellwhyte')\",\""+ + baseURL+"/People('scottketchum')\""+ + "]"+ + "}"; + ContentResponse response = http.POST(baseURL + "/People") + .content(content(payload), "application/json") + .header("Prefer", "return=minimal") + .send(); + // the below woud be 204, if minimal was not supplied + assertEquals(204, response.getStatus()); + + response = http.GET(baseURL+"/People('olingo')/Friends"); + assertEquals(200, response.getStatus()); + + JsonNode node = getJSONNode(response); + assertEquals("$metadata#People", node.get("@odata.context").asText()); + assertTrue(node.get("value").isArray()); + assertEquals("scottketchum", ((ArrayNode)node.get("value")).get(1).get("UserName").asText()); + } + + @Test + public void testUpdatePrimitiveProperty() throws Exception { + String payload = "{" + + " \"value\":\"Pilar Ackerman\"" + + "}"; + + String editUrl = baseURL + "/People('russellwhyte')/FirstName"; + ContentResponse response = http.newRequest(editUrl) + .content(content(payload), "application/json") + .method(HttpMethod.PUT) + .send(); + assertEquals(204, response.getStatus()); + + response = http.GET(editUrl); + assertEquals(200, response.getStatus()); + JsonNode node = getJSONNode(response); + assertEquals("$metadata#People('russellwhyte')/FirstName", node.get("@odata.context").asText()); + assertEquals("Pilar Ackerman", node.get("value").asText()); + } + + @Test + public void testUpdatePrimitiveArrayProperty() throws Exception { + String payload = "{" + + " \"value\": [\n" + + " \"olingo@apache.com\"\n" + + " ]" + + "}"; + + String editUrl = baseURL + "/People('russellwhyte')/Emails"; + ContentResponse response = http.newRequest(editUrl) + .content(content(payload), "application/json") + .method(HttpMethod.PUT) + .send(); + assertEquals(204, response.getStatus()); + + response = http.GET(editUrl); + assertEquals(200, response.getStatus()); + JsonNode node = getJSONNode(response); + assertEquals("$metadata#People('russellwhyte')/Emails", node.get("@odata.context").asText()); + assertTrue(node.get("value").isArray()); + assertEquals("olingo@apache.com", ((ArrayNode)node.get("value")).get(0).asText()); + } + + @Test + public void testDeleteProperty() throws Exception { + String editUrl = baseURL + "/People('russellwhyte')/FirstName"; + ContentResponse response = http.GET(editUrl); + assertEquals(200, response.getStatus()); + JsonNode node = getJSONNode(response); + assertEquals("Russell", node.get("value").asText()); + + response = http.newRequest(editUrl) + .method(HttpMethod.DELETE) + .send(); + assertEquals(204, response.getStatus()); + + response = http.GET(editUrl); + assertEquals(204, response.getStatus()); + } + + @Test + public void testReadNavigationPropertyEntityCollection() throws Exception { + String editUrl = baseURL + "/People('russellwhyte')/Friends"; + ContentResponse response = http.GET(editUrl); + assertEquals(200, response.getStatus()); + + JsonNode node = getJSONNode(response); + assertEquals("$metadata#People", node.get("@odata.context").asText()); + + JsonNode person = ((ArrayNode)node.get("value")).get(0); + assertEquals("scottketchum", person.get("UserName").asText()); + } + + @Test + public void testReadNavigationPropertyEntityCollection2() throws Exception { + String editUrl = baseURL + "/People('russellwhyte')/Friends('scottketchum')/Trips"; + ContentResponse response = http.GET(editUrl); + assertEquals(200, response.getStatus()); + + JsonNode node = getJSONNode(response); + assertEquals("$metadata#People('russellwhyte')/Friends('scottketchum')/Trips", + node.get("@odata.context").asText()); + assertTrue(node.get("value").isArray()); + assertEquals("1001", ((ArrayNode)node.get("value")).get(0).get("TripId").asText()); + } + + @Test + public void testReadNavigationPropertyEntity() throws Exception { + String editUrl = baseURL + "/People('russellwhyte')/Trips(1003)"; + ContentResponse response = http.GET(editUrl); + assertEquals(200, response.getStatus()); + + JsonNode node = getJSONNode(response); + assertEquals("$metadata#People('russellwhyte')/Trips/$entity", + node.get("@odata.context").asText()); + assertEquals("f94e9116-8bdd-4dac-ab61-08438d0d9a71", node.get("ShareId").asText()); + } + + @Test + public void testReadNavigationPropertyEntityNotExisting() throws Exception { + String editUrl = baseURL + "/People('russellwhyte')/Trips(9999)"; + ContentResponse response = http.GET(editUrl); + assertEquals(204, response.getStatus()); + } + + @Test + public void testReadNavigationPropertyEntitySetNotExisting() throws Exception { + String editUrl = baseURL + "/People('jhondoe')/Trips"; + ContentResponse response = http.GET(editUrl); + assertEquals(200, response.getStatus()); + JsonNode node = getJSONNode(response); + assertEquals("$metadata#People('jhondoe')/Trips", + node.get("@odata.context").asText()); + assertEquals(0, ((ArrayNode)node.get("value")).size()); + } + + @Test + public void testBadNavigationProperty() throws Exception { + String editUrl = baseURL + "/People('russellwhyte')/Unknown"; + ContentResponse response = http.GET(editUrl); + assertEquals(404, response.getStatus()); + } + + @Test + public void testReadNavigationPropertyEntityProperty() throws Exception { + String editUrl = baseURL + "/People('russellwhyte')/Trips(1003)/PlanItems(5)/ConfirmationCode"; + ContentResponse response = http.GET(editUrl); + assertEquals(200, response.getStatus()); + + JsonNode node = getJSONNode(response); + assertEquals("$metadata#People('russellwhyte')/Trips(1003)/PlanItems(5)/ConfirmationCode", + node.get("@odata.context").asText()); + + assertEquals("JH58494", node.get("value").asText()); + } + + @Test + public void testReadNavigationPropertyEntityMultipleDerivedTypes() throws Exception { + String editUrl = baseURL + "/People('russellwhyte')/Trips(1003)/PlanItems"; + ContentResponse response = http.GET(editUrl); + assertEquals(200, response.getStatus()); + + JsonNode node = getJSONNode(response); + assertEquals("$metadata#People('russellwhyte')/Trips(1003)/PlanItems", + node.get("@odata.context").asText()); + + assertEquals("#Microsoft.OData.SampleService.Models.TripPin.Flight", + ((ArrayNode) node.get("value")).get(0).get("@odata.type").asText()); + } + + @Test + public void testReadNavigationPropertyEntityCoolectionDerivedFilter() throws Exception { + String editUrl = baseURL + + "/People('russellwhyte')/Trips(1003)/PlanItems/Microsoft.OData.SampleService.Models.TripPin.Event"; + ContentResponse response = http.GET(editUrl); + assertEquals(200, response.getStatus()); + + JsonNode node = getJSONNode(response); + assertEquals("$metadata#People('russellwhyte')/Trips(1003)/PlanItems/" + + "Microsoft.OData.SampleService.Models.TripPin.Event", + node.get("@odata.context").asText()); + + assertEquals("#Microsoft.OData.SampleService.Models.TripPin.Event", + ((ArrayNode) node.get("value")).get(0).get("@odata.type").asText()); + } + + @Test + public void testReadNavigationPropertyEntityDerivedFilter() throws Exception { + String editUrl = baseURL+ "/People('russellwhyte')/Trips(1003)/PlanItems(56)/" + + "Microsoft.OData.SampleService.Models.TripPin.Event"; + ContentResponse response = http.GET(editUrl); + assertEquals(200, response.getStatus()); + JsonNode node = getJSONNode(response); + assertEquals("$metadata#People('russellwhyte')/Trips(1003)/PlanItems/" + + "Microsoft.OData.SampleService.Models.TripPin.Event/$entity", + node.get("@odata.context").asText()); + + assertEquals("#Microsoft.OData.SampleService.Models.TripPin.Event", node.get("@odata.type").asText()); + assertEquals("56", node.get("PlanItemId").asText()); + } + + @Test + public void testUpdateReference() throws Exception { + ContentResponse response = http.GET(baseURL+"/People('ronaldmundy')/Photo/$ref"); + assertEquals(200, response.getStatus()); + JsonNode node = getJSONNode(response); + assertEquals("/Photos(12)", node.get("@odata.id").asText()); + + String msg = "{\n" + + "\"@odata.id\": \"/Photos(11)\"\n" + + "}"; + String editUrl = baseURL + "/People('ronaldmundy')/Photo/$ref"; + response = http.newRequest(editUrl) + .method(HttpMethod.PUT) + .content(content(msg)) + .header("Content-Type", "application/json;odata.metadata=minimal") + .send(); + assertEquals(204, response.getStatus()); + + response = http.GET(baseURL+"/People('ronaldmundy')/Photo/$ref"); + assertEquals(200, response.getStatus()); + node = getJSONNode(response); + assertEquals("/Photos(11)", node.get("@odata.id").asText()); + } + + @Test + public void testAddDelete2ReferenceCollection() throws Exception { + // add + String msg = "{\n" + + "\"@odata.id\": \"/People('russellwhyte')\"\n" + + "}"; + String editUrl = baseURL + "/People('vincentcalabrese')/Friends/$ref"; + ContentResponse response = http.newRequest(editUrl) + .method(HttpMethod.POST) + .content(content(msg)) + .header("Content-Type", "application/json;odata.metadata=minimal") + .send(); + assertEquals(204, response.getStatus()); + + // get + response = http.GET(editUrl); + assertEquals(200, response.getStatus()); + JsonNode node = getJSONNode(response); + assertEquals("/People('russellwhyte')", + ((ArrayNode) node.get("value")).get(2).get("@odata.id").asText()); + + //delete + response = http.newRequest(editUrl+"?$id="+baseURL+"/People('russellwhyte')") + .method(HttpMethod.DELETE) + .content(content(msg)) + .header("Content-Type", "application/json;odata.metadata=minimal") + .send(); + assertEquals(204, response.getStatus()); + + // get + response = http.GET(editUrl); + assertEquals(200, response.getStatus()); + node = getJSONNode(response); + assertNull("/People('russellwhyte')", ((ArrayNode) node.get("value")).get(2)); + } + + @Test + public void testDeleteReference() throws Exception { + String editUrl = baseURL + "/People('russellwhyte')/Photo/$ref"; + ContentResponse response = http.GET(editUrl); + assertEquals(200, response.getStatus()); + + response = http.newRequest(editUrl) + .method(HttpMethod.DELETE) + .send(); + assertEquals(204, response.getStatus()); + + response = http.GET(editUrl); + assertEquals(204, response.getStatus()); + } + + @Test + public void testCrossJoin() throws Exception { + String editUrl = baseURL + "/$crossjoin(People,Airlines)"; + ContentResponse response = http.GET(editUrl); + assertEquals(200, response.getStatus()); + } + + public static ContentProvider content(final String msg) { + return new ContentProvider() { + boolean hasNext = true; + + @Override + public Iterator iterator() { + return new Iterator() { + @Override + public boolean hasNext() { + return hasNext; + } + @Override + public ByteBuffer next() { + hasNext = false; + return ByteBuffer.wrap(msg.getBytes()); + } + @Override + public void remove() { + } + }; + } + @Override + public long getLength() { + return msg.length(); + } + }; + } +} diff --git a/lib/server-core-ext/src/test/java/org/apache/olingo/server/example/TripPinServlet.java b/lib/server-core-ext/src/test/java/org/apache/olingo/server/example/TripPinServlet.java new file mode 100644 index 000000000..2c05d6522 --- /dev/null +++ b/lib/server-core-ext/src/test/java/org/apache/olingo/server/example/TripPinServlet.java @@ -0,0 +1,75 @@ +/* + * 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.server.example; + +import java.io.FileReader; +import java.io.IOException; +import java.util.Collections; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.xml.stream.XMLStreamException; + +import org.apache.olingo.commons.api.edm.provider.EdmProvider; +import org.apache.olingo.server.api.OData; +import org.apache.olingo.server.api.ODataHttpHandler; +import org.apache.olingo.server.api.ServiceMetadata; +import org.apache.olingo.server.core.MetadataParser; +import org.apache.olingo.server.core.OData4Impl; + +public class TripPinServlet extends HttpServlet { + private static final long serialVersionUID = 2663595419366214401L; + private TripPinDataModel dataModel; + + @Override + public void init(ServletConfig config) throws ServletException { + super.init(config); + } + + @Override + public void service(HttpServletRequest request, HttpServletResponse response) throws IOException { + OData odata = OData4Impl.newInstance(); + MetadataParser parser = new MetadataParser(); + EdmProvider edmProvider = null; + + try { + edmProvider = parser.buildEdmProvider(new FileReader("src/test/resources/trippin.xml")); + } catch (XMLStreamException e) { + throw new IOException(e); + } + + ServiceMetadata metadata = odata.createServiceMetadata(edmProvider, Collections.EMPTY_LIST); + + ODataHttpHandler handler = odata.createHandler(metadata); + + if (this.dataModel == null) { + try { + this.dataModel = new TripPinDataModel(metadata); + } catch (Exception e) { + throw new IOException("Failed to load data for TripPin Service"); + } + } + + handler.register(new TripPinHandler(this.dataModel)); + handler.process(request, response); + } +} diff --git a/lib/server-core-ext/src/test/resources/OlingoOrangeTM.png b/lib/server-core-ext/src/test/resources/OlingoOrangeTM.png new file mode 100644 index 000000000..d614f1a46 Binary files /dev/null and b/lib/server-core-ext/src/test/resources/OlingoOrangeTM.png differ diff --git a/lib/server-core-ext/src/test/resources/airlines.json b/lib/server-core-ext/src/test/resources/airlines.json new file mode 100644 index 000000000..78eccdc81 --- /dev/null +++ b/lib/server-core-ext/src/test/resources/airlines.json @@ -0,0 +1,64 @@ +{ + "value":[ + { + "AirlineCode":"AA", + "Name":"American Airlines" + }, + { + "AirlineCode":"FM", + "Name":"Shanghai Airline" + }, + { + "AirlineCode":"MU", + "Name":"China Eastern Airlines" + }, + { + "AirlineCode":"AF", + "Name":"Air France" + }, + { + "AirlineCode":"AZ", + "Name":"Alitalia" + }, + { + "AirlineCode":"AC", + "Name":"Air Canada" + }, + { + "AirlineCode":"OS", + "Name":"Austrian Airlines" + }, + { + "AirlineCode":"TK", + "Name":"Turkish Airlines" + }, + { + "AirlineCode":"JL", + "Name":"Japan Airlines" + }, + { + "AirlineCode":"SQ", + "Name":"Singapore Airlines" + }, + { + "AirlineCode":"KE", + "Name":"Korean Air" + }, + { + "AirlineCode":"CZ", + "Name":"China Southern" + }, + { + "AirlineCode":"AK", + "Name":"AirAsia" + }, + { + "AirlineCode":"HX", + "Name":"Hong Kong Airlines" + }, + { + "AirlineCode":"EK", + "Name":"Emirates" + } + ] +} \ No newline at end of file diff --git a/lib/server-core-ext/src/test/resources/airports.json b/lib/server-core-ext/src/test/resources/airports.json new file mode 100644 index 000000000..c06a5cf48 --- /dev/null +++ b/lib/server-core-ext/src/test/resources/airports.json @@ -0,0 +1,394 @@ +{ + "value":[ + { + "IcaoCode":"KSFO", + "Name":"San Francisco International Airport", + "IataCode":"SFO", + "Location":{ + "Address":"South McDonnell Road, San Francisco, CA 94128", + "City":{ + "CountryRegion":"United States", + "Name":"San Francisco", + "Region":"California" + }, + "Loc":{ + "type":"Point", + "coordinates":[ + -122.374722222222, + 37.6188888888889 + ], + "crs":{ + "type":"name", + "properties":{ + "name":"EPSG:4326" + } + } + } + } + }, + { + "IcaoCode":"KLAX", + "Name":"Los Angeles International Airport", + "IataCode":"LAX", + "Location":{ + "Address":"1 World Way, Los Angeles, CA, 90045", + "City":{ + "CountryRegion":"United States", + "Name":"Los Angeles", + "Region":"California" + }, + "Loc":{ + "type":"Point", + "coordinates":[ + -118.408055555556, + 33.9425 + ], + "crs":{ + "type":"name", + "properties":{ + "name":"EPSG:4326" + } + } + } + } + }, + { + "IcaoCode":"ZSSS", + "Name":"Shanghai Hongqiao International Airport", + "IataCode":"SHA", + "Location":{ + "Address":"Hongqiao Road 2550, Changning District", + "City":{ + "CountryRegion":"China", + "Name":"Shanghai", + "Region":"Shanghai" + }, + "Loc":{ + "type":"Point", + "coordinates":[ + 121.336111111111, + 31.1977777777778 + ], + "crs":{ + "type":"name", + "properties":{ + "name":"EPSG:4326" + } + } + } + } + }, + { + "IcaoCode":"ZBAA", + "Name":"Beijing Capital International Airport", + "IataCode":"PEK", + "Location":{ + "Address":"Airport Road, Chaoyang District, Beijing, 100621", + "City":{ + "CountryRegion":"China", + "Name":"Beijing", + "Region":"Beijing" + }, + "Loc":{ + "type":"Point", + "coordinates":[ + 116.584444444444, + 40.08 + ], + "crs":{ + "type":"name", + "properties":{ + "name":"EPSG:4326" + } + } + } + } + }, + { + "IcaoCode":"KJFK", + "Name":"John F. Kennedy International Airport", + "IataCode":"JFK", + "Location":{ + "Address":"Jamaica, New York, NY 11430", + "City":{ + "CountryRegion":"United States", + "Name":"New York City", + "Region":"New York" + }, + "Loc":{ + "type":"Point", + "coordinates":[ + -73.7788888888889, + 40.6397222222222 + ], + "crs":{ + "type":"name", + "properties":{ + "name":"EPSG:4326" + } + } + } + } + }, + { + "IcaoCode":"LIRA", + "Name":"Rome Ciampino Airport", + "IataCode":"CIA", + "Location":{ + "Address":"Via Appia Nuova, 1651", + "City":{ + "CountryRegion":"Italy", + "Name":"Rome", + "Region":"" + }, + "Loc":{ + "type":"Point", + "coordinates":[ + 12.5947222222222, + 41.7991666666667 + ], + "crs":{ + "type":"name", + "properties":{ + "name":"EPSG:4326" + } + } + } + } + }, + { + "IcaoCode":"CYYZ", + "Name":"Toronto Pearson International Airport", + "IataCode":"YYZ", + "Location":{ + "Address":"6301 Silver Dart Dr Mississauga", + "City":{ + "CountryRegion":"Canada", + "Name":"Mississauga", + "Region":"Ontario" + }, + "Loc":{ + "type":"Point", + "coordinates":[ + -79.6305555555555, + 43.6772222222222 + ], + "crs":{ + "type":"name", + "properties":{ + "name":"EPSG:4326" + } + } + } + } + }, + { + "IcaoCode":"YSSY", + "Name":"Sydney Airport", + "IataCode":"SYD", + "Location":{ + "Address":"Airport Dr Sydney NSW 2020", + "City":{ + "CountryRegion":"Australia", + "Name":"Sydney", + "Region":"" + }, + "Loc":{ + "type":"Point", + "coordinates":[ + 151.177222222222, + -33.9461111111111 + ], + "crs":{ + "type":"name", + "properties":{ + "name":"EPSG:4326" + } + } + } + } + }, + { + "IcaoCode":"LTBA", + "Name":"Istanbul Ataturk Airport", + "IataCode":"IST", + "Location":{ + "Address":"Ye\u015filk\u00f6y Mh.34149 \u0130stanbul", + "City":{ + "CountryRegion":"Turkey", + "Name":"Istanbul", + "Region":"" + }, + "Loc":{ + "type":"Point", + "coordinates":[ + 28.8211111111111, + 40.9766666666667 + ], + "crs":{ + "type":"name", + "properties":{ + "name":"EPSG:4326" + } + } + } + } + }, + { + "IcaoCode":"WSSS", + "Name":"Singapore Changi Airport", + "IataCode":"SIN", + "Location":{ + "Address":"Airport Blvd, Singapore", + "City":{ + "CountryRegion":"Singapore", + "Name":"Changi", + "Region":"" + }, + "Loc":{ + "type":"Point", + "coordinates":[ + 103.987222222222, + 1.35555555555556 + ], + "crs":{ + "type":"name", + "properties":{ + "name":"EPSG:4326" + } + } + } + } + }, + { + "IcaoCode":"OMAA", + "Name":"Abu Dhabi International Airport", + "IataCode":"AUH", + "Location":{ + "Address":"Sheik Maktoum Bin Rashid Rd Abu Dhabi", + "City":{ + "CountryRegion":"United Arab Emirates", + "Name":"Abu Dhabi", + "Region":"" + }, + "Loc":{ + "type":"Point", + "coordinates":[ + 54.6511111111111, + 24.4327777777778 + ], + "crs":{ + "type":"name", + "properties":{ + "name":"EPSG:4326" + } + } + } + } + }, + { + "IcaoCode":"ZGGG", + "Name":"Guangzhou Baiyun International Airport", + "IataCode":"CAN", + "Location":{ + "Address":"Jichang Road, Renhezhen, Huadu", + "City":{ + "CountryRegion":"China", + "Name":"Guangzhou", + "Region":"Guangdong" + }, + "Loc":{ + "type":"Point", + "coordinates":[ + 113.265833333333, + 23.1841666666667 + ], + "crs":{ + "type":"name", + "properties":{ + "name":"EPSG:4326" + } + } + } + } + }, + { + "IcaoCode":"KORD", + "Name":"O'Hare International Airport", + "IataCode":"ORD", + "Location":{ + "Address":"10000 W O'Hare Ave", + "City":{ + "CountryRegion":"United States", + "Name":"Chicago", + "Region":"Illinois" + }, + "Loc":{ + "type":"Point", + "coordinates":[ + -87.9044444444445, + 41.9794444444444 + ], + "crs":{ + "type":"name", + "properties":{ + "name":"EPSG:4326" + } + } + } + } + }, + { + "IcaoCode":"KATL", + "Name":"Hartsfield-Jackson Atlanta International Airport", + "IataCode":"ATL", + "Location":{ + "Address":"6000 N Terminal Pkwy", + "City":{ + "CountryRegion":"United States", + "Name":"Atlanta", + "Region":"Georgia" + }, + "Loc":{ + "type":"Point", + "coordinates":[ + -84.4269444444444, + 33.6402777777778 + ], + "crs":{ + "type":"name", + "properties":{ + "name":"EPSG:4326" + } + } + } + } + }, + { + "IcaoCode":"KSEA", + "Name":"Seattle-Tacoma International Airport", + "IataCode":"SEA", + "Location":{ + "Address":"17801 International Blvd", + "City":{ + "CountryRegion":"United States", + "Name":"SeaTac", + "Region":"Washington" + }, + "Loc":{ + "type":"Point", + "coordinates":[ + -122.309166666667, + 47.4488888888889 + ], + "crs":{ + "type":"name", + "properties":{ + "name":"EPSG:4326" + } + } + } + } + } + ] +} \ No newline at end of file diff --git a/lib/server-core-ext/src/test/resources/event.json b/lib/server-core-ext/src/test/resources/event.json new file mode 100644 index 000000000..eb19dde83 --- /dev/null +++ b/lib/server-core-ext/src/test/resources/event.json @@ -0,0 +1,157 @@ +{ + "value":[ + { + "PlanItemId": 50, + "Description": "Client Meeting", + "ConfirmationCode": "4372899DD", + "StartsAt": "2014-01-02T13:00:00Z", + "EndsAt": "2014-01-02T16:00:00Z", + "Duration": "PT3H", + "OccursAt": + { + "BuildingInfo": "Regus Business Center", + "City": + { + "Name": "New York City", + "CountryRegion": "United States", + "Region": "New York" + }, + "Address": "100 Church Street, 8th Floor, Manhattan, 10007" + } + }, + { + "PlanItemId": 51, + "Description": "Visit the Brooklyn Bridge Park", + "ConfirmationCode": "4372899AA", + "StartsAt": "2014-01-01T15:00:00Z", + "EndsAt": "2014-01-01T16:00:00Z", + "Duration": "PT1H", + "OccursAt": + { + "BuildingInfo": "Brooklyn Bridge Park, at Fulton Ferry Landing", + "City": + { + "Name": "New York City", + "CountryRegion": "United States", + "Region": "New York" + }, + "Address": "Main St Dumbo Brooklyn 11201" + } + }, + { + "PlanItemId": 52, + "Description": "Empire State Building", + "ConfirmationCode": "4372899BB", + "StartsAt": "2014-01-03T10:00:00Z", + "EndsAt": "2014-01-03T12:00:00Z", + "Duration": "PT2H", + "OccursAt": + { + "BuildingInfo": "Empire State Building", + "City": + { + "Name": "New York City", + "CountryRegion": "United States", + "Region": "New York" + }, + "Address": "Empire State Building, 350 5th Ave" + } + }, + { + "PlanItemId": 53, + "Description": "Coney Island", + "ConfirmationCode": "4372899CC", + "StartsAt": "2014-01-03T14:00:00Z", + "EndsAt": "2014-01-03T20:00:00Z", + "Duration": "PT6H", + "OccursAt": + { + "BuildingInfo": "", + "City": + { + "Name": "New York City", + "CountryRegion": "United States", + "Region": "New York" + }, + "Address": "1208 Surf Ave, Brooklyn" + } + }, + { + "PlanItemId": 54, + "Description": "Shopping at Times Square", + "ConfirmationCode": "4372899DD", + "StartsAt": "2014-01-04T10:00:00Z", + "EndsAt": "2014-01-04T15:00:00Z", + "Duration": "PT5H", + "OccursAt": + { + "BuildingInfo": "", + "City": + { + "Name": "New York City", + "CountryRegion": "United States", + "Region": "New York" + }, + "Address": "Broadway, 7th Avenue, 42nd and 47th Streets" + } + }, + { + "PlanItemId": 55, + "Description": "Dinner", + "ConfirmationCode": "4372899EE", + "StartsAt": "2014-02-02T18:00:00Z", + "EndsAt": "2014-02-02T21:00:00Z", + "Duration": "PT3H", + "OccursAt": + { + "Address": "10 Beijing Street, 100000", + "City": + { + "Name": "Beijing", + "CountryRegion": "China", + "Region": "Beijing" + }, + "BuildingInfo": "Beijing Restaurant" + } + }, + { + "PlanItemId": 56, + "Description": "Dinner", + "ConfirmationCode": "4372899FF", + "StartsAt": "2014-02-02T18:00:00Z", + "EndsAt": "2014-02-02T21:00:00Z", + "Duration": "PT3H", + "OccursAt": + { + "BuildingInfo": "Beijing Restaurant", + "City": + { + "Name": "Beijing", + "CountryRegion": "China", + "Region": "Beijing" + }, + "Address": "10 Beijing Street, 100000" + } + }, + { + "PlanItemId": 57, + "Description": "Dinner", + "ConfirmationCode": "4372899GG", + "StartsAt": "2014-02-02T18:00:00Z", + "EndsAt": "2014-02-02T21:00:00Z", + "Duration": "PT3H", + "OccursAt": + { + "BuildingInfo": "Beijing Restaurant", + "City": + { + "Name": "Beijing", + "CountryRegion": "China", + "Region": "Beijing" + }, + "Address": "10 Beijing Street, 100000" + } + } + ] +} + \ No newline at end of file diff --git a/lib/server-core-ext/src/test/resources/flight-links.json b/lib/server-core-ext/src/test/resources/flight-links.json new file mode 100644 index 000000000..17741d0b0 --- /dev/null +++ b/lib/server-core-ext/src/test/resources/flight-links.json @@ -0,0 +1,52 @@ +{ + "value":[ + { + "PlanItemId": 1, + "Airline": "AA", + "From": "ORD", + "To": "JFK" + }, + { + "PlanItemId": 2, + "Airline": "AA", + "From": "JFK", + "To": "ORD" + }, + { + "PlanItemId": 3, + "Airline": "FM", + "From": "SHA", + "To": "PEK" + }, + { + "PlanItemId": 4, + "Airline": "MU", + "From": "PEK", + "To": "SHA" + }, + { + "PlanItemId": 5, + "Airline": "FM", + "From": "SHA", + "To": "PEK" + }, + { + "PlanItemId": 6, + "Airline": "MU", + "From": "PEK", + "To": "SHA" + }, + { + "PlanItemId": 7, + "Airline": "FM", + "From": "SHA", + "To": "PEK" + }, + { + "PlanItemId": 8, + "Airline": "MU", + "From": "PEK", + "To": "SHA" + } + ] +} \ No newline at end of file diff --git a/lib/server-core-ext/src/test/resources/flight.json b/lib/server-core-ext/src/test/resources/flight.json new file mode 100644 index 000000000..af0699857 --- /dev/null +++ b/lib/server-core-ext/src/test/resources/flight.json @@ -0,0 +1,66 @@ +{ + "value":[ + { + "PlanItemId": 1, + "ConfirmationCode": "JH58493", + "FlightNumber": "AA26", + "StartsAt": "2014-01-01T06:15:00Z", + "EndsAt": "2014-01-01T11:35:00Z" + }, + { + "PlanItemId": 2, + "ConfirmationCode": "JH38143", + "FlightNumber": "AA4035", + "StartsAt": "2014-01-04T17:55:00Z", + "EndsAt": "2014-01-04T20:45:00Z" + }, + { + "PlanItemId": 3, + "ConfirmationCode": "JH58494", + "FlightNumber": "FM1930", + "StartsAt": "2014-02-01T08:00:00Z", + "EndsAt": "2014-02-01T09:20:00Z", + "SeatNumber": "B11" + }, + { + "PlanItemId": 4, + "ConfirmationCode": "JH58495", + "FlightNumber": "MU1930", + "StartsAt": "2014-02-10T15:00:00Z", + "EndsAt": "2014-02-10T16:30:00Z", + "SeatNumber": "A32" + }, + { + "PlanItemId": 5, + "ConfirmationCode": "JH58494", + "FlightNumber": "FM1930", + "StartsAt": "2014-02-01T08:00:00Z", + "EndsAt": "2014-02-01T09:20:00Z", + "SeatNumber": "B11" + }, + { + "PlanItemId": 6, + "ConfirmationCode": "JH58495", + "FlightNumber": "MU1930", + "StartsAt": "2014-02-10T15:00:00Z", + "EndsAt": "2014-02-10T16:30:00Z", + "SeatNumber": "A32" + }, + { + "PlanItemId": 7, + "ConfirmationCode": "JH58494", + "FlightNumber": "FM1930", + "StartsAt": "2014-02-01T08:00:00Z", + "EndsAt": "2014-02-01T09:20:00Z", + "SeatNumber": "B12" + }, + { + "PlanItemId": 8, + "ConfirmationCode": "JH58495", + "FlightNumber": "MU1930", + "StartsAt": "2014-02-10T16:30:00Z", + "EndsAt": "2014-02-10T16:30:00Z", + "SeatNumber": "A33" + } + ] +} \ No newline at end of file diff --git a/lib/server-core-ext/src/test/resources/people-links.json b/lib/server-core-ext/src/test/resources/people-links.json new file mode 100644 index 000000000..878d6ce23 --- /dev/null +++ b/lib/server-core-ext/src/test/resources/people-links.json @@ -0,0 +1,94 @@ +{ + "value":[ + { + "UserName":"russellwhyte", + "Friends": ["scottketchum", "ronaldmundy", "javieralfred", "angelhuffman"], + "Trips": [1001, 1003, 1007], + "Photo": 1 + }, + { + "UserName":"scottketchum", + "Friends": ["russellwhyte", "ronaldmundy"], + "Trips": [1001, 2004], + "Photo": 11 + }, + { + "UserName":"ronaldmundy", + "Friends": ["russellwhyte", "scottketchum"], + "Trips": [3009], + "Photo": 12 + }, + { + "UserName":"javieralfred", + "Friends": ["willieashmore", "vincentcalabrese", "georginabarlow"], + "Trips": [4005] + }, + { + "UserName":"willieashmore", + "Friends": ["javieralfred", "vincentcalabrese"], + "Trips": [5007, 5008] + }, + { + "UserName":"vincentcalabrese", + "Friends": ["javieralfred", "willieashmore"], + "Trips": [7010] + }, + { + "UserName":"clydeguess", + "Friends": ["keithpinckney", "ursulabright"], + "Trips": [8011] + }, + { + "UserName":"keithpinckney", + "Friends": ["clydeguess", "marshallgaray"], + "Trips": [] + }, + { + "UserName":"marshallgaray", + "Friends": ["keithpinckney", "elainestewart", "jonirosales"] + }, + { + "UserName":"elainestewart", + "Friends": ["marshallgaray"] + }, + { + "UserName":"salliesampson", + "Friends": [""], + "Trips": [13012] + }, + { + "UserName":"jonirosales", + "Friends": ["marshallgaray"], + "Trips": [14013] + }, + { + "UserName":"georginabarlow", + "Friends": ["javieralfred"] + }, + { + "UserName":"angelhuffman", + "Friends": ["russellwhyte"], + "Trips": [16014] + }, + { + "UserName":"laurelosborn", + "Friends": ["sandyosborn"] + }, + { + "UserName":"sandyosborn", + "Friends": ["laurelosborn"] + }, + { + "UserName":"ursulabright", + "Friends": ["keithpinckney"] + }, + { + "UserName":"genevievereeves", + "Friends": ["kristakemp"] + }, + { + "UserName":"kristakemp", + "Friends": ["genevievereeves"] + } + ] +} diff --git a/lib/server-core-ext/src/test/resources/people.json b/lib/server-core-ext/src/test/resources/people.json new file mode 100644 index 000000000..64699bbc2 --- /dev/null +++ b/lib/server-core-ext/src/test/resources/people.json @@ -0,0 +1,323 @@ +{ + "value":[ + { + "UserName":"russellwhyte", + "FirstName":"Russell", + "LastName":"Whyte", + "Emails":[ + "Russell@example.com", + "Russell@contoso.com" + ], + "AddressInfo":[ + { + "Address":"187 Suffolk Ln.", + "City":{ + "CountryRegion":"United States", + "Name":"Boise", + "Region":"ID" + } + } + ], + "Gender":"0", + "Concurrency":635585295719432047 + }, + { + "UserName":"scottketchum", + "FirstName":"Scott", + "LastName":"Ketchum", + "Emails":[ + "Scott@example.com" + ], + "AddressInfo":[ + { + "Address":"2817 Milton Dr.", + "City":{ + "CountryRegion":"United States", + "Name":"Albuquerque", + "Region":"NM" + } + } + ], + "Gender":"0", + "Concurrency":635585295719432047 + }, + { + "UserName":"ronaldmundy", + "FirstName":"Ronald", + "LastName":"Mundy", + "Emails":[ + "Ronald@example.com", + "Ronald@contoso.com" + ], + "AddressInfo":[ + + ], + "Gender":"0", + "Concurrency":635585295719432047 + }, + { + "UserName":"javieralfred", + "FirstName":"Javier", + "LastName":"Alfred", + "Emails":[ + "Javier@example.com", + "Javier@contoso.com" + ], + "AddressInfo":[ + { + "Address":"89 Jefferson Way Suite 2", + "City":{ + "CountryRegion":"United States", + "Name":"Portland", + "Region":"WA" + } + } + ], + "Gender":"0", + "Concurrency":635585295719432047 + }, + { + "UserName":"willieashmore", + "FirstName":"Willie", + "LastName":"Ashmore", + "Emails":[ + "Willie@example.com", + "Willie@contoso.com" + ], + "AddressInfo":[ + + ], + "Gender":"0", + "Concurrency":635585295719432047 + }, + { + "UserName":"vincentcalabrese", + "FirstName":"Vincent", + "LastName":"Calabrese", + "Emails":[ + "Vincent@example.com", + "Vincent@contoso.com" + ], + "AddressInfo":[ + { + "Address":"55 Grizzly Peak Rd.", + "City":{ + "CountryRegion":"United States", + "Name":"Butte", + "Region":"MT" + } + } + ], + "Gender":"0", + "Concurrency":635585295719432047 + }, + { + "UserName":"clydeguess", + "FirstName":"Clyde", + "LastName":"Guess", + "Emails":[ + "Clyde@example.com" + ], + "AddressInfo":[ + + ], + "Gender":"0", + "Concurrency":635585295719432047 + }, + { + "UserName":"keithpinckney", + "FirstName":"Keith", + "LastName":"Pinckney", + "Emails":[ + "Keith@example.com", + "Keith@contoso.com" + ], + "AddressInfo":[ + + ], + "Gender":"0", + "Concurrency":635585295719432047 + }, + { + "UserName":"marshallgaray", + "FirstName":"Marshall", + "LastName":"Garay", + "Emails":[ + "Marshall@example.com", + "Marshall@contoso.com" + ], + "AddressInfo":[ + + ], + "Gender":"0", + "Concurrency":635585295719432047 + }, + { + "UserName":"ryantheriault", + "FirstName":"Ryan", + "LastName":"Theriault", + "Emails":[ + "Ryan@example.com", + "Ryan@contoso.com" + ], + "AddressInfo":[ + + ], + "Gender":"0", + "Concurrency":635585295719432047 + }, + { + "UserName":"elainestewart", + "FirstName":"Elaine", + "LastName":"Stewart", + "Emails":[ + "Elaine@example.com", + "Elaine@contoso.com" + ], + "AddressInfo":[ + + ], + "Gender":"1", + "Concurrency":635585295719432047 + }, + { + "UserName":"salliesampson", + "FirstName":"Sallie", + "LastName":"Sampson", + "Emails":[ + "Sallie@example.com", + "Sallie@contoso.com" + ], + "AddressInfo":[ + { + "Address":"87 Polk St. Suite 5", + "City":{ + "CountryRegion":"United States", + "Name":"San Francisco", + "Region":"CA" + } + }, + { + "Address":"89 Chiaroscuro Rd.", + "City":{ + "CountryRegion":"United States", + "Name":"Portland", + "Region":"OR" + } + } + ], + "Gender":"1", + "Concurrency":635585295719432047 + }, + { + "UserName":"jonirosales", + "FirstName":"Joni", + "LastName":"Rosales", + "Emails":[ + "Joni@example.com", + "Joni@contoso.com" + ], + "AddressInfo":[ + + ], + "Gender":"1", + "Concurrency":635585295719432047 + }, + { + "UserName":"georginabarlow", + "FirstName":"Georgina", + "LastName":"Barlow", + "Emails":[ + "Georgina@example.com", + "Georgina@contoso.com" + ], + "AddressInfo":[ + + ], + "Gender":"1", + "Concurrency":635585295719432047 + }, + { + "UserName":"angelhuffman", + "FirstName":"Angel", + "LastName":"Huffman", + "Emails":[ + "Angel@example.com" + ], + "AddressInfo":[ + + ], + "Gender":"1", + "Concurrency":635585295719432047 + }, + { + "UserName":"laurelosborn", + "FirstName":"Laurel", + "LastName":"Osborn", + "Emails":[ + "Laurel@example.com", + "Laurel@contoso.com" + ], + "AddressInfo":[ + + ], + "Gender":"1", + "Concurrency":635585295719432047 + }, + { + "UserName":"sandyosborn", + "FirstName":"Sandy", + "LastName":"Osborn", + "Emails":[ + "Sandy@example.com", + "Sandy@contoso.com" + ], + "AddressInfo":[ + + ], + "Gender":"1", + "Concurrency":635585295719432047 + }, + { + "UserName":"ursulabright", + "FirstName":"Ursula", + "LastName":"Bright", + "Emails":[ + "Ursula@example.com", + "Ursula@contoso.com" + ], + "AddressInfo":[ + + ], + "Gender":"1", + "Concurrency":635585295719432047 + }, + { + "UserName":"genevievereeves", + "FirstName":"Genevieve", + "LastName":"Reeves", + "Emails":[ + "Genevieve@example.com", + "Genevieve@contoso.com" + ], + "AddressInfo":[ + + ], + "Gender":"1", + "Concurrency":635585295719432047 + }, + { + "UserName":"kristakemp", + "FirstName":"Krista", + "LastName":"Kemp", + "Emails":[ + "Krista@example.com" + ], + "AddressInfo":[ + + ], + "Gender":"1", + "Concurrency":635585295719432047 + } + ] +} diff --git a/lib/server-core-ext/src/test/resources/photos.json b/lib/server-core-ext/src/test/resources/photos.json new file mode 100644 index 000000000..127d19532 --- /dev/null +++ b/lib/server-core-ext/src/test/resources/photos.json @@ -0,0 +1,64 @@ +{ + "value":[ + { + "Id":1, + "Name":"My Photo 1" + }, + { + "Id":11, + "Name":"Trip Photo 11" + }, + { + "Id":12, + "Name":"Trip Photo 12" + }, + { + "Id":13, + "Name":"Trip Photo 13" + }, + { + "Id":14, + "Name":"Trip Photo 14" + }, + { + "Id":2, + "Name":"My Photo 2" + }, + { + "Id":21, + "Name":"Trip Photo 21" + }, + { + "Id":22, + "Name":"Trip Photo 22" + }, + { + "Id":23, + "Name":"Trip Photo 23" + }, + { + "Id":24, + "Name":"Trip Photo 24" + }, + { + "Id":3, + "Name":"My Photo 3" + }, + { + "Id":31, + "Name":"Trip Photo 31" + }, + { + "Id":32, + "Name":"Trip Photo 32" + }, + { + "Id":33, + "Name":"Trip Photo 33" + }, + { + "Id":34, + "Name":"Trip Photo 34" + } + ] +} \ No newline at end of file diff --git a/lib/server-core-ext/src/test/resources/trip-links.json b/lib/server-core-ext/src/test/resources/trip-links.json new file mode 100644 index 000000000..df8f9b8d3 --- /dev/null +++ b/lib/server-core-ext/src/test/resources/trip-links.json @@ -0,0 +1,28 @@ +{ + "value":[ + { + "TripId": 1001, + "Flights": [1, 2], + "Events": [51, 52, 53, 54, 55], + "Photos": [21, 22] + }, + { + "TripId":2, + "Flights": [3, 4], + "Events": [55], + "Photos": [13, 14] + }, + { + "TripId": 1003, + "Flights": [5, 6], + "Events": [56, 57], + "Photos": [23, 24] + }, + { + "TripId": 2004, + "Flights": [7, 8], + "Events": [55, 57], + "Photos": [33, 34] + } + ] +} diff --git a/lib/server-core-ext/src/test/resources/trip.json b/lib/server-core-ext/src/test/resources/trip.json new file mode 100644 index 000000000..79c610403 --- /dev/null +++ b/lib/server-core-ext/src/test/resources/trip.json @@ -0,0 +1,224 @@ +{ + "value":[ + { + "TripId": 1001, + "ShareId": "9d9b2fa0-efbf-490e-a5e3-bac8f7d47354", + "Description": "Trip from San Francisco to New York City. Nice trip with two friends. It is a 4 days' trip. We actually had a client meeting, but we also took one to go sightseeings in New York.", + "Name": "Trip in US", + "Budget": 3000.0, + "StartsAt":"2014-01-01T00:00:00Z", + "EndsAt": "2014-01-04T00:00:00Z", + "Tags": ["Trip in New York", "business","sightseeing"] + }, + { + "TripId":2, + "ShareId": "f94e9116-8bdd-4dac-ab61-08438d0d9a71", + "Description": "Trip from Shanghai to Beijing", + "Name":"Trip in Beijing", + "Budget": 3000.0, + "Tags": ["Travel", "Beijing"], + "StartsAt": "2014-02-01T00:00:00Z", + "EndsAt": "2014-02-04T00:00:00Z" + }, + { + "TripId": 3, + "ShareId": "9ce142c3-5fd6-4a71-848e-5220ebf1e9f3", + "Name": "Honeymoon", + "Budget": 800.0, + "Description": "Happy honeymoon trip", + "Tags": ["Travel", "honeymoon"], + "StartsAt": "2014-02-01T00:00:00Z", + "EndsAt": "2014-02-04T00:00:00Z" + }, + { + "TripId": 4, + "ShareId": "4CCFB043-C79C-44EF-8CFE-CD493CED6654", + "Name": "Business trip to OData", + "Budget": 324.6, + "Description": "Business trip to OData", + "Tags": ["business", "odata"], + "StartsAt": "2013-01-01T00:00:00Z", + "EndsAt": "2013-01-04T00:00:00Z" + }, + { + "TripId": 5, + "ShareId": "4546F419-0070-45F7-BA2C-19E4BC3647E1", + "Name": "Travel trip in US", + "Budget": 1250.0, + "Description": "Travel trip in US", + "Tags": ["travel", "overseas"], + "StartsAt": "2013-01-19T00:00:00Z", + "EndsAt": "2013-01-28T00:00:00Z" + }, + { + "TripId": 6, + "ShareId": "26F0E8F6-657A-4561-BF3B-719366EF04FA", + "Name": "Study music in Europe", + "Budget": 3200.0, + "Description": "Study music in Europe", + "Tags": ["study", "overseas"], + "StartsAt": "2013-03-01T00:00:00Z", + "EndsAt": "2013-05-04T00:00:00Z" + }, + { + "TripId": 7, + "ShareId": "2E77BF06-A354-454B-8BCA-5F004C1AFB59", + "Name": "Conference talk about OData", + "Budget": 2120.55, + "Description": "Conference talk about ODatan", + "Tags": ["odata", "overseas"], + "StartsAt": "2013-07-02T00:00:00Z", + "EndsAt": "2013-07-05T00:00:00Z" + }, + { + "TripId": 8, + "ShareId": "E6E23FB2-C428-439E-BDAB-9283482F49F0", + "Name": "Vocation at hometown", + "Budget": 1500.0, + "Description": "Vocation at hometown", + "Tags": ["voaction"], + "StartsAt": "2013-10-01T00:00:00Z", + "EndsAt": "2013-10-05T00:00:00Z" + }, + { + "TripId": 9, + "ShareId": "FAE31279-35CE-4119-9BDC-53F6E19DD1C5", + "Name": "Business trip for tech training", + "Budget": 100.0, + "Description": "Business trip for tech training", + "Tags": ["business"], + "StartsAt": "2013-09-01T00:00:00Z", + "EndsAt": "2013-09-04T00:00:00Z" + }, + { + "TripId": 3009, + "ShareId": "dd6a09c0-e59b-4745-8612-f4499b676c47", + "Name": "Gradutaion trip", + "Budget": 6000.0, + "Description": "Gradution trip with friends", + "Tags": ["Travel"], + "StartsAt": "2013-05-01T00:00:00Z", + "EndsAt": "2013-05-08T00:00:00Z" + }, + { + "TripId": 2004, + "ShareId": "f94e9116-8bdd-4dac-ab61-08438d0d9a71", + "Name": "Trip in Beijing", + "Budget": 11000.0, + "Description": "Trip from Shanghai to Beijing", + "Tags": ["Travel", "Beijing"], + "StartsAt": "2014-02-01T00:00:00Z", + "EndsAt": "2013-02-02T00:00:00Z" + }, + { + "TripId": 4005, + "ShareId": "f94e9116-8bdd-4dac-ab61-08438d0d9a71", + "Name": "Trip in Beijing", + "Budget": 800.0, + "Description": "Trip from Shanghai to Beijing", + "Tags": ["Travel", "Beijing"], + "StartsAt": "2014-02-01T00:00:00Z", + "EndsAt": "2014-02-04T00:00:00Z" + }, + { + "TripId": 5007, + "ShareId": "5ae142c3-5ad6-4a71-768e-5220ebf1e9f3", + "Name": "Business Trip", + "Budget": 3800.5, + "Description": "This is my first business trip", + "Tags": ["business", "first"], + "StartsAt": "2014-02-01T00:00:00Z", + "EndsAt": "2014-02-04T00:00:00Z" + }, + { + "TripId": 5008, + "ShareId": "9ce32ac3-5fd6-4a72-848e-2250ebf1e9f3", + "Name": "Trip in Europe", + "Budget": 2000.0, + "Description": "The trip is currently in plan.", + "Tags": ["Travel", "plan"], + "StartsAt": "2014-02-01T00:00:00Z", + "EndsAt": "2014-02-04T00:00:00Z" + }, + { + "TripId": 1003, + "Name": "Trip in Beijing", + "Budget": 2000.0, + "ShareId": "f94e9116-8bdd-4dac-ab61-08438d0d9a71", + "Description": "Trip from Shanghai to Beijing", + "Tags": ["Travel", "Beijing"], + "StartsAt": "2014-02-01T00:00:00Z", + "EndsAt": "2014-02-04T00:00:00Z" + }, + { + "TripId": 2004, + "ShareId": "f94e9116-8bdd-4dac-ab61-08438d0d9a71", + "Name": "Trip in Beijing", + "Budget": 11000.0, + "Description": "Trip from Shanghai to Beijing", + "Tags": ["Travel", "Beijing"], + "StartsAt": "2014-02-01T00:00:00Z", + "EndsAt": "2014-02-04T00:00:00Z" + }, + { + "TripId": 1007, + "ShareId": "9ce142c3-5fd6-4a71-848e-5220ebf1e9f3", + "Name": "Honeymoon", + "Budget": 2650.0, + "Description": "Happy honeymoon trip", + "Tags": ["Travel", "honeymoon"], + "StartsAt": "2014-02-01T00:00:00Z", + "EndsAt": "2014-02-04T00:00:00Z" + }, + { + "TripId": 7010, + "ShareId": "dd6a09c0-e59b-4745-8612-f4499b676c47", + "Name": "Gradutaion trip", + "Budget": 1000.0, + "Description": "Gradution trip with friends", + "Tags": ["Travel"], + "StartsAt": "2013-05-01T00:00:00Z", + "EndsAt": "2013-05-08T00:00:00Z" + }, + { + "TripId": 8011, + "ShareId": "a88f675d-9199-4392-9656-b08e3b46df8a", + "Name": "Study trip", + "Budget": 1550.3, + "Description": "This is a 2 weeks study trip", + "Tags": ["study"], + "StartsAt": "2014-01-01T00:00:00Z", + "EndsAt": "2013-01-14T00:00:00Z" + }, + { + "TripId": 13012, + "ShareId": "a88f675d-9199-4392-9656-b08e3b46df8a", + "Name": "Study trip", + "Budget": 600.0, + "Description": "This is a 2 weeks study trip", + "Tags": ["study"], + "StartsAt": "2014-01-01T00:00:00Z", + "EndsAt": "2013-01-14T00:00:00Z" + }, + { + "TripId": 14013, + "ShareId": "a88f675d-9199-4392-9656-b08e3b46df8a", + "Name": "Study trip", + "Budget": 2000.0, + "Description": "This is a 2 weeks study trip", + "Tags": ["study"], + "StartsAt": "2014-01-01T00:00:00Z", + "EndsAt": "2013-01-14T00:00:00Z" + }, + { + "TripId": 16014, + "ShareId": "cb0b8acb-79cb-4127-8316-772bc4302824", + "Name": "DIY Trip", + "Budget": 1500.3, + "Description": "This is a DIY trip", + "Tags": ["Travel", "DIY"], + "StartsAt": "2011-02-11T00:00:00Z", + "EndsAt": "2011-02-14T00:00:00Z" + } + ] +} diff --git a/lib/server-core-ext/src/test/resources/trippin.xml b/lib/server-core-ext/src/test/resources/trippin.xml new file mode 100644 index 000000000..5970793ef --- /dev/null +++ b/lib/server-core-ext/src/test/resources/trippin.xml @@ -0,0 +1,356 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Org.OData.Core.V1.Permission/Read + + + + + + image/jpeg + + + + + + + + + + Org.OData.Core.V1.Permission/Read + + + + + + + + + + + + + + + + + + + + + Org.OData.Core.V1.Permission/Read + + + + + + + + + + + + Org.OData.Core.V1.Permission/Read + + + + + + + + + + + + + + + Org.OData.Core.V1.Permission/Read + + + + + + + + + + + + + + + + + + + + + + + + + + + Org.OData.Core.V1.Permission/Read + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Org.OData.Capabilities.V1.SearchExpressions/none + + + + + + + + + + + + + + + + + + + + + + Concurrency + + + + + + + Org.OData.Capabilities.V1.NavigationType/None + + + + + + + Org.OData.Capabilities.V1.NavigationType/Recursive + + + + + + + + + + + Org.OData.Capabilities.V1.SearchExpressions/none + + + + + + + + + Trips + Friends + + + + + + + + + + + + Org.OData.Capabilities.V1.SearchExpressions/none + + + + + + + + + + + + + + + + + + + Org.OData.Capabilities.V1.SearchExpressions/none + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Org.OData.Capabilities.V1.ConformanceLevelType/Advanced + + + + application/json;odata.metadata=full;IEEE754Compatible=false;odata.streaming=true + application/json;odata.metadata=minimal;IEEE754Compatible=false;odata.streaming=true + application/json;odata.metadata=none;IEEE754Compatible=false;odata.streaming=true + + + + + + + contains + endswith + startswith + length + indexof + substring + tolower + toupper + trim + concat + year + month + day + hour + minute + second + round + floor + ceiling + cast + isof + + + + + + \ No newline at end of file diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializer.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializer.java index 74c73c8f1..a66570f0f 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializer.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializer.java @@ -20,6 +20,8 @@ package org.apache.olingo.server.core.deserializer.json; import java.io.IOException; import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -65,6 +67,7 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; public class ODataJsonDeserializer implements ODataDeserializer { @@ -267,8 +270,8 @@ public class ODataJsonDeserializer implements ODataDeserializer { node.remove(toRemove); } - private void consumeEntityProperties(final EdmEntityType edmEntityType, final ObjectNode node, final EntityImpl - entity) throws DeserializerException { + private void consumeEntityProperties(final EdmEntityType edmEntityType, final ObjectNode node, + final EntityImpl entity) throws DeserializerException { List propertyNames = edmEntityType.getPropertyNames(); for (String propertyName : propertyNames) { JsonNode jsonNode = node.get(propertyName); @@ -409,7 +412,7 @@ public class ODataJsonDeserializer implements ODataDeserializer { case ENUM: value = readEnumValue(name, type, isNullable, maxLength, precision, scale, isUnicode, mapping, jsonNode); - property.setValue(ValueType.PRIMITIVE, value); + property.setValue(ValueType.ENUM, value); break; case COMPLEX: value = readComplexNode(name, type, isNullable, jsonNode); @@ -706,4 +709,81 @@ public class ODataJsonDeserializer implements ODataDeserializer { DeserializerException.MessageKeys.NOT_IMPLEMENTED); } } + + @Override + public Property property(InputStream stream, EdmProperty edmProperty) + throws DeserializerException { + try { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.configure(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY, true); + JsonParser parser = new JsonFactory(objectMapper).createParser(stream); + final ObjectNode tree = parser.getCodec().readTree(parser); + + Property property = null; + JsonNode jsonNode = tree.get(Constants.VALUE); + if (jsonNode != null) { + property = consumePropertyNode(edmProperty.getName(), edmProperty.getType(), + edmProperty.isCollection(), + edmProperty.isNullable(), edmProperty.getMaxLength(), edmProperty.getPrecision(), edmProperty.getScale(), + edmProperty.isUnicode(), edmProperty.getMapping(), + jsonNode); + tree.remove(Constants.VALUE); + } else { + property = consumePropertyNode(edmProperty.getName(), edmProperty.getType(), + edmProperty.isCollection(), + edmProperty.isNullable(), edmProperty.getMaxLength(), edmProperty.getPrecision(), edmProperty.getScale(), + edmProperty.isUnicode(), edmProperty.getMapping(), + tree); + } + return property; + } catch (JsonParseException e) { + throw new DeserializerException("An JsonParseException occurred", e, + DeserializerException.MessageKeys.JSON_SYNTAX_EXCEPTION); + } catch (JsonMappingException e) { + throw new DeserializerException("Duplicate property detected", e, + DeserializerException.MessageKeys.DUPLICATE_PROPERTY); + } catch (IOException e) { + throw new DeserializerException("An IOException occurred", e, DeserializerException.MessageKeys.IO_EXCEPTION); + } + } + + public List entityReferences(InputStream stream) throws DeserializerException { + try { + ArrayList parsedValues = new ArrayList(); + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.configure(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY, true); + JsonParser parser = new JsonFactory(objectMapper).createParser(stream); + final ObjectNode tree = parser.getCodec().readTree(parser); + final String key = "@odata.id"; + JsonNode jsonNode = tree.get(Constants.VALUE); + if (jsonNode != null) { + if (jsonNode.isArray()) { + ArrayNode arrayNode = (ArrayNode)jsonNode; + Iterator it = arrayNode.iterator(); + while(it.hasNext()) { + parsedValues.add(new URI(it.next().get(key).asText())); + } + } else { + parsedValues.add(new URI(jsonNode.asText())); + } + tree.remove(Constants.VALUE); + // if this is value there can be only one property + return parsedValues; + } + parsedValues.add(new URI(tree.get(key).asText())); + return parsedValues; + } catch (JsonParseException e) { + throw new DeserializerException("An JsonParseException occurred", e, + DeserializerException.MessageKeys.JSON_SYNTAX_EXCEPTION); + } catch (JsonMappingException e) { + throw new DeserializerException("Duplicate property detected", e, + DeserializerException.MessageKeys.DUPLICATE_PROPERTY); + } catch (IOException e) { + throw new DeserializerException("An IOException occurred", e, + DeserializerException.MessageKeys.IO_EXCEPTION); + } catch (URISyntaxException e) { + throw new DeserializerException("failed to read @odata.id", e, + DeserializerException.MessageKeys.UNKOWN_CONTENT); + } + } } diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializer.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializer.java index aab662454..5956e8286 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializer.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializer.java @@ -6,9 +6,9 @@ * 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 @@ -41,6 +41,7 @@ import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeException; import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind; import org.apache.olingo.commons.api.edm.EdmProperty; import org.apache.olingo.commons.api.edm.EdmStructuredType; +import org.apache.olingo.commons.api.edm.FullQualifiedName; import org.apache.olingo.commons.api.format.ODataFormat; import org.apache.olingo.commons.core.edm.primitivetype.EdmPrimitiveTypeFactory; import org.apache.olingo.server.api.ODataServerError; @@ -127,7 +128,8 @@ public class ODataJsonSerializer implements ODataSerializer { } @Override - public InputStream entityCollection(final EdmEntityType entityType, final EntitySet entitySet, + public InputStream entityCollection(final ServiceMetadata metadata, + final EdmEntityType entityType, final EntitySet entitySet, final EntityCollectionSerializerOptions options) throws SerializerException { CircleStreamBuffer buffer = new CircleStreamBuffer(); try { @@ -145,8 +147,9 @@ public class ODataJsonSerializer implements ODataSerializer { json.writeNumberField(Constants.JSON_COUNT, entitySet.getCount()); } json.writeFieldName(Constants.VALUE); - writeEntitySet(entityType, entitySet, - options == null ? null : options.getExpand(), options == null ? null : options.getSelect(), json); + writeEntitySet(metadata, entityType, entitySet, options == null ? null : options.getExpand(), + options == null ? null : options.getSelect(), + options == null ? false : options.onlyReferences(), json); if (entitySet.getNext() != null) { json.writeStringField(Constants.JSON_NEXT_LINK, entitySet.getNext().toASCIIString()); } @@ -159,14 +162,16 @@ public class ODataJsonSerializer implements ODataSerializer { } @Override - public InputStream entity(final EdmEntityType entityType, final Entity entity, - final EntitySerializerOptions options) throws SerializerException { + public InputStream entity(final ServiceMetadata metadata, final EdmEntityType entityType, + final Entity entity, final EntitySerializerOptions options) throws SerializerException { final ContextURL contextURL = checkContextURL(options == null ? null : options.getContextURL()); CircleStreamBuffer buffer = new CircleStreamBuffer(); try { JsonGenerator json = new JsonFactory().createGenerator(buffer.getOutputStream()); - writeEntity(entityType, entity, contextURL, - options == null ? null : options.getExpand(), options == null ? null : options.getSelect(), json); + writeEntity(metadata, entityType, entity, contextURL, + options == null ? null : options.getExpand(), + options == null ? null : options.getSelect(), + options == null ? false: options.onlyReferences(), json); json.close(); } catch (final IOException e) { throw new SerializerException("An I/O exception occurred.", e, @@ -184,18 +189,26 @@ public class ODataJsonSerializer implements ODataSerializer { return contextURL; } - protected void writeEntitySet(final EdmEntityType entityType, final EntitySet entitySet, - final ExpandOption expand, final SelectOption select, final JsonGenerator json) - throws IOException, SerializerException { + protected void writeEntitySet(final ServiceMetadata metadata, final EdmEntityType entityType, + final EntitySet entitySet, final ExpandOption expand, final SelectOption select, + final boolean onlyReference, final JsonGenerator json) throws IOException, + SerializerException { json.writeStartArray(); for (final Entity entity : entitySet.getEntities()) { - writeEntity(entityType, entity, null, expand, select, json); + if (onlyReference) { + json.writeStartObject(); + json.writeStringField(Constants.JSON_ID, entity.getId().toASCIIString()); + json.writeEndObject(); + } else { + writeEntity(metadata, entityType, entity, null, expand, select, false, json); + } } json.writeEndArray(); } - protected void writeEntity(final EdmEntityType entityType, final Entity entity, final ContextURL contextURL, - final ExpandOption expand, final SelectOption select, final JsonGenerator json) + protected void writeEntity(final ServiceMetadata metadata, final EdmEntityType entityType, + final Entity entity, final ContextURL contextURL, final ExpandOption expand, + final SelectOption select, boolean onlyReference, final JsonGenerator json) throws IOException, SerializerException { json.writeStartObject(); if (format != ODataFormat.JSON_NO_METADATA) { @@ -214,9 +227,63 @@ public class ODataJsonSerializer implements ODataSerializer { } } } - writeProperties(entityType, entity.getProperties(), select, json); - writeNavigationProperties(entityType, entity, expand, json); - json.writeEndObject(); + if (onlyReference) { + json.writeStringField(Constants.JSON_ID, entity.getId().toASCIIString()); + } else { + EdmEntityType resolvedType = resolveEntityType(metadata, entityType, entity.getType()); + if (!resolvedType.equals(entityType)) { + json.writeStringField(Constants.JSON_TYPE, "#"+entity.getType()); + } + writeProperties(resolvedType, entity.getProperties(), select, json); + writeNavigationProperties(metadata, resolvedType, entity, expand, json); + json.writeEndObject(); + } + } + + protected EdmEntityType resolveEntityType(ServiceMetadata metadata, EdmEntityType baseType, + String derivedTypeName) throws SerializerException { + if (baseType.getFullQualifiedName().getFullQualifiedNameAsString().equals(derivedTypeName)) { + return baseType; + } + EdmEntityType derivedType = metadata.getEdm().getEntityType(new FullQualifiedName(derivedTypeName)); + if (derivedType == null) { + throw new SerializerException("EntityType not found", + SerializerException.MessageKeys.UNKNOWN_TYPE, derivedTypeName); + } + EdmEntityType type = derivedType.getBaseType(); + while (type != null) { + if (type.getFullQualifiedName().getFullQualifiedNameAsString() + .equals(baseType.getFullQualifiedName().getFullQualifiedNameAsString())) { + return derivedType; + } + type = type.getBaseType(); + } + throw new SerializerException("Wrong base type", + SerializerException.MessageKeys.WRONG_BASE_TYPE, derivedTypeName, baseType + .getFullQualifiedName().getFullQualifiedNameAsString()); + } + + protected EdmComplexType resolveComplexType(ServiceMetadata metadata, EdmComplexType baseType, + String derivedTypeName) throws SerializerException { + if (baseType.getFullQualifiedName().getFullQualifiedNameAsString().equals(derivedTypeName)) { + return baseType; + } + EdmComplexType derivedType = metadata.getEdm().getComplexType(new FullQualifiedName(derivedTypeName)); + if (derivedType == null) { + throw new SerializerException("Complex Type not found", + SerializerException.MessageKeys.UNKNOWN_TYPE, derivedTypeName); + } + EdmComplexType type = derivedType.getBaseType(); + while (type != null) { + if (type.getFullQualifiedName().getFullQualifiedNameAsString() + .equals(baseType.getFullQualifiedName().getFullQualifiedNameAsString())) { + return derivedType; + } + type = type.getBaseType(); + } + throw new SerializerException("Wrong base type", + SerializerException.MessageKeys.WRONG_BASE_TYPE, derivedTypeName, baseType + .getFullQualifiedName().getFullQualifiedNameAsString()); } protected void writeProperties(final EdmStructuredType type, final List properties, @@ -235,8 +302,9 @@ public class ODataJsonSerializer implements ODataSerializer { } } - protected void writeNavigationProperties(final EdmStructuredType type, final Linked linked, - final ExpandOption expand, final JsonGenerator json) throws SerializerException, IOException { + protected void writeNavigationProperties(final ServiceMetadata metadata, + final EdmStructuredType type, final Linked linked, final ExpandOption expand, + final JsonGenerator json) throws SerializerException, IOException { if (ExpandSelectHelper.hasExpand(expand)) { final boolean expandAll = ExpandSelectHelper.isExpandAll(expand); final Set expanded = expandAll ? null : @@ -251,7 +319,7 @@ public class ODataJsonSerializer implements ODataSerializer { throw new SerializerException("Expand options $ref and $levels are not supported.", SerializerException.MessageKeys.NOT_IMPLEMENTED); } - writeExpandedNavigationProperty(property, navigationLink, + writeExpandedNavigationProperty(metadata, property, navigationLink, innerOptions == null ? null : innerOptions.getExpandOption(), innerOptions == null ? null : innerOptions.getSelectOption(), json); @@ -260,7 +328,8 @@ public class ODataJsonSerializer implements ODataSerializer { } } - protected void writeExpandedNavigationProperty(final EdmNavigationProperty property, final Link navigationLink, + protected void writeExpandedNavigationProperty(final ServiceMetadata metadata, + final EdmNavigationProperty property, final Link navigationLink, final ExpandOption innerExpand, final SelectOption innerSelect, JsonGenerator json) throws IOException, SerializerException { json.writeFieldName(property.getName()); @@ -269,13 +338,15 @@ public class ODataJsonSerializer implements ODataSerializer { json.writeStartArray(); json.writeEndArray(); } else { - writeEntitySet(property.getType(), navigationLink.getInlineEntitySet(), innerExpand, innerSelect, json); + writeEntitySet(metadata, property.getType(), navigationLink.getInlineEntitySet(), innerExpand, + innerSelect, false, json); } } else { if (navigationLink == null || navigationLink.getInlineEntity() == null) { json.writeNull(); } else { - writeEntity(property.getType(), navigationLink.getInlineEntity(), null, innerExpand, innerSelect, json); + writeEntity(metadata, property.getType(), navigationLink.getInlineEntity(), null, + innerExpand, innerSelect, false, json); } } } @@ -316,6 +387,11 @@ public class ODataJsonSerializer implements ODataSerializer { } else if (property.isComplex()) { writeComplexValue((EdmComplexType) edmProperty.getType(), property.asComplex().getValue(), selectedPaths, json); + } else if (property.isEnum()) { + writePrimitive((EdmPrimitiveType) edmProperty.getType(), property, + edmProperty.isNullable(), edmProperty.getMaxLength(), + edmProperty.getPrecision(), edmProperty.getScale(), edmProperty.isUnicode(), + json); } else { throw new SerializerException("Property type not yet supported!", SerializerException.MessageKeys.UNSUPPORTED_PROPERTY_TYPE, edmProperty.getName()); @@ -467,8 +543,8 @@ public class ODataJsonSerializer implements ODataSerializer { } @Override - public InputStream complex(final EdmComplexType type, final Property property, - final ComplexSerializerOptions options) throws SerializerException { + public InputStream complex(final ServiceMetadata metadata, final EdmComplexType type, + final Property property, final ComplexSerializerOptions options) throws SerializerException { final ContextURL contextURL = checkContextURL(options == null ? null : options.getContextURL()); CircleStreamBuffer buffer = new CircleStreamBuffer(); try { @@ -477,11 +553,15 @@ public class ODataJsonSerializer implements ODataSerializer { if (contextURL != null) { json.writeStringField(Constants.JSON_CONTEXT, ContextURLBuilder.create(contextURL).toASCIIString()); } + EdmComplexType resolvedType = resolveComplexType(metadata, type, property.getType()); + if (!resolvedType.equals(type)) { + json.writeStringField(Constants.JSON_TYPE, "#"+property.getType()); + } final List values = property.isNull() ? Collections. emptyList() : property.asComplex().getValue(); writeProperties(type, values, options == null ? null : options.getSelect(), json); if (!property.isNull() && property.isComplex()) { - writeNavigationProperties(type, property.asComplex(), + writeNavigationProperties(metadata, type, property.asComplex(), options == null ? null : options.getExpand(), json); } json.writeEndObject(); @@ -523,8 +603,8 @@ public class ODataJsonSerializer implements ODataSerializer { } @Override - public InputStream complexCollection(final EdmComplexType type, final Property property, - final ComplexSerializerOptions options) throws SerializerException { + public InputStream complexCollection(final ServiceMetadata metadata, final EdmComplexType type, + final Property property, final ComplexSerializerOptions options) throws SerializerException { final ContextURL contextURL = checkContextURL(options == null ? null : options.getContextURL()); CircleStreamBuffer buffer = new CircleStreamBuffer(); try { diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/utils/ContextURLBuilder.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/utils/ContextURLBuilder.java index 4a3f82aae..ac3375976 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/utils/ContextURLBuilder.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/utils/ContextURLBuilder.java @@ -66,7 +66,14 @@ public final class ContextURLBuilder { if (contextURL.getEntitySetOrSingletonOrType() != null) { throw new IllegalArgumentException("ContextURL: $ref with Entity Set"); } - result.append('#').append(ContextURL.Suffix.REFERENCE.getRepresentation()); + if(contextURL.isCollection()) { + result.append('#'); + result.append("Collection(") + .append(ContextURL.Suffix.REFERENCE.getRepresentation()) + .append(")"); + } else { + result.append('#').append(ContextURL.Suffix.REFERENCE.getRepresentation()); + } } else if (contextURL.getSuffix() != null) { if (contextURL.getEntitySetOrSingletonOrType() == null) { throw new IllegalArgumentException("ContextURL: Suffix without preceding Entity Set!"); diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/xml/ODataXmlSerializerImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/xml/ODataXmlSerializerImpl.java index acd1dedd0..34756c106 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/xml/ODataXmlSerializerImpl.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/xml/ODataXmlSerializerImpl.java @@ -6,9 +6,9 @@ * 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 @@ -87,14 +87,15 @@ public class ODataXmlSerializerImpl implements ODataSerializer { } @Override - public InputStream entity(final EdmEntityType entityType, final Entity entity, - final EntitySerializerOptions options) throws SerializerException { + public InputStream entity(final ServiceMetadata metadata, final EdmEntityType entityType, + final Entity entity, final EntitySerializerOptions options) throws SerializerException { throw new SerializerException("Entity serialization not implemented for XML format", SerializerException.MessageKeys.NOT_IMPLEMENTED); } @Override - public InputStream entityCollection(final EdmEntityType entityType, final EntitySet entitySet, + public InputStream entityCollection(final ServiceMetadata metadata, + final EdmEntityType entityType, final EntitySet entitySet, final EntityCollectionSerializerOptions options) throws SerializerException { throw new SerializerException("Entityset serialization not implemented for XML format", SerializerException.MessageKeys.NOT_IMPLEMENTED); @@ -114,8 +115,8 @@ public class ODataXmlSerializerImpl implements ODataSerializer { } @Override - public InputStream complex(final EdmComplexType type, final Property property, - final ComplexSerializerOptions options) throws SerializerException { + public InputStream complex(final ServiceMetadata metadata, final EdmComplexType type, + final Property property, final ComplexSerializerOptions options) throws SerializerException { throw new SerializerException("Serialization not implemented for XML format.", SerializerException.MessageKeys.NOT_IMPLEMENTED); } @@ -128,8 +129,8 @@ public class ODataXmlSerializerImpl implements ODataSerializer { } @Override - public InputStream complexCollection(final EdmComplexType type, final Property property, - final ComplexSerializerOptions options) throws SerializerException { + public InputStream complexCollection(final ServiceMetadata metadata, final EdmComplexType type, + final Property property, final ComplexSerializerOptions options) throws SerializerException { throw new SerializerException("Serialization not implemented for XML format.", SerializerException.MessageKeys.NOT_IMPLEMENTED); } diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/UriResourceActionImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/UriResourceActionImpl.java index 82fe74392..a78d79f1a 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/UriResourceActionImpl.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/UriResourceActionImpl.java @@ -6,9 +6,9 @@ * 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 @@ -56,12 +56,18 @@ public class UriResourceActionImpl extends UriResourceTypedImpl implements UriRe @Override public boolean isCollection() { - return action.getReturnType() !=null && action.getReturnType().isCollection(); + if (action.getReturnType() != null) { + return action.getReturnType().isCollection(); + } + return false; } @Override public EdmType getType() { - return action.getReturnType() == null ? null : action.getReturnType().getType(); + if (action.getReturnType() != null) { + return action.getReturnType().getType(); + } + return null; } @Override diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/validator/UriValidator.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/validator/UriValidator.java index 0b3a5f924..cf5493873 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/validator/UriValidator.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/validator/UriValidator.java @@ -6,9 +6,9 @@ * 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 @@ -60,7 +60,7 @@ public class UriValidator { /* entitySetCount 7 */ { true , false, false, false, false, false, true, false, false, false, false }, /* entity 8 */ { false, true , true , false, false, false, false, true , false, false, false }, /* mediaStream 9 */ { false, false, false, false, false, false, false, false, false, false, false }, - /* references 10 */ { true , true , false, false, false, true , true , false, true , true , true }, + /* references 10 */ { true , true , false, true, false, true , true , false, true , true , true }, /* reference 11 */ { false, true , false, false, false, false, false, false, false, false, false }, /* propertyComplex 12 */ { false, true , true , false, false, false, false, true , false, false, false }, /* propertyComplexCollection 13 */ { true , true , true , false, true , true , false, true , true , true , true }, @@ -78,7 +78,7 @@ public class UriValidator { /* GET 0 */ { true , true , true , true, true , true , true , true , true , true , true }, /* POST 0 */ { true , false , true , false, false , true , false , true , false , false , false }, /* PUT 0 */ { false , false , false , false, false , false , false , false , false , false , false }, - /* DELETE 0 */ { false , false , false , false, false , false, false , false, false , false , false }, + /* DELETE 0 */ { false , false , false , true, false , false, false , false, false , false , false }, /* PATCH 0 */ { false , false , false , false, false , false , false , false , false , false , false } }; //CHECKSTYLE:ON diff --git a/lib/server-core/src/main/resources/server-core-exceptions-i18n.properties b/lib/server-core/src/main/resources/server-core-exceptions-i18n.properties index 76266ea9c..b8254c3d2 100644 --- a/lib/server-core/src/main/resources/server-core-exceptions-i18n.properties +++ b/lib/server-core/src/main/resources/server-core-exceptions-i18n.properties @@ -96,6 +96,8 @@ SerializerException.INCONSISTENT_PROPERTY_TYPE=An inconsistency has been detecte SerializerException.MISSING_PROPERTY=The non-nullable property '%1$s' is missing. SerializerException.WRONG_PROPERTY_VALUE=The value '%2$s' is not valid for property '%1$s'. SerializerException.WRONG_PRIMITIVE_VALUE=The value '%2$s' is not valid for the primitive type '%1$s' and the given facets. +SerializerException.UNKNOWN_TYPE=Type '%1s' not found in metadata. +SerializerException.WRONG_BASE_TYPE=Type '%1s' is not derived from '%2s'. DeserializerException.NOT_IMPLEMENTED=The requested deserialization method has not been implemented yet. DeserializerException.IO_EXCEPTION=An I/O exception occurred. diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializerBasicTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializerBasicTest.java index f3e22efca..a301c3d78 100644 --- a/lib/server-core/src/test/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializerBasicTest.java +++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializerBasicTest.java @@ -6,9 +6,9 @@ * 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 @@ -18,8 +18,13 @@ */ package org.apache.olingo.server.core.deserializer.json; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import java.io.ByteArrayInputStream; +import java.net.URI; +import java.util.List; + import org.apache.olingo.commons.api.format.ODataFormat; import org.apache.olingo.server.api.OData; import org.apache.olingo.server.api.deserializer.ODataDeserializer; @@ -41,4 +46,33 @@ public class ODataJsonDeserializerBasicTest { assertNotNull(deserializer); deserializer = null; } + + @Test + public void testReadingCollectionProperties() throws Exception { + String payload = "{\n" + + " \"@odata.context\": \"http://host/service/$metadata#Collection($ref)\",\n" + + " \"value\": [\n" + + " { \"@odata.id\": \"Orders(10643)\" },\n" + + " { \"@odata.id\": \"Orders(10759)\" }\n" + + " ]\n" + + "}"; + ODataDeserializer deserializer = OData.newInstance().createDeserializer(ODataFormat.JSON); + List values = deserializer.entityReferences(new ByteArrayInputStream(payload.getBytes())); + assertEquals(2, values.size()); + assertEquals("Orders(10643)", values.get(0).toASCIIString()); + assertEquals("Orders(10759)", values.get(1).toASCIIString()); + } + + @Test + public void testReadingProperties() throws Exception { + String payload = "{\n" + + " \"@odata.context\": \"http://host/service/$metadata#$ref\",\n" + + " \"@odata.id\": \"Orders(10643)\"\n" + + "}"; + ODataDeserializer deserializer = OData.newInstance().createDeserializer(ODataFormat.JSON); + List values = deserializer.entityReferences(new ByteArrayInputStream(payload + .getBytes())); + assertEquals(1, values.size()); + assertEquals("Orders(10643)", values.get(0).toASCIIString()); + } } diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializerTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializerTest.java index 742c5d54e..1e4537f8d 100644 --- a/lib/server-core/src/test/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializerTest.java +++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializerTest.java @@ -6,9 +6,9 @@ * 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 @@ -49,14 +49,17 @@ public class ODataJsonSerializerTest { final ODataJsonSerializer serializer = new ODataJsonSerializer(ODataFormat.APPLICATION_JSON); final ComplexSerializerOptions options = ComplexSerializerOptions.with() .contextURL(ContextURL.with().selectList("ComplexCollection").build()).build(); - final InputStream in = serializer.complexCollection(ComplexTypeHelper.createType(), complexCollection, options); + final InputStream in = serializer.complexCollection(null, ComplexTypeHelper.createType(), + complexCollection, options); final BufferedReader reader = new BufferedReader(new InputStreamReader(in)); String line; while ((line = reader.readLine()) != null) { if (line.contains("value")) { - assertEquals("{\"@odata.context\":\"$metadata(ComplexCollection)\",\"value\":" - + "[{\"prop1\":\"test1\",\"prop2\":\"test11\"},{\"prop1\":\"test2\",\"prop2\":\"test22\"}]}", line); + assertEquals( + "{\"@odata.context\":\"$metadata(ComplexCollection)\",\"value\":" + + "[{\"prop1\":\"test1\",\"prop2\":\"test11\"},{\"prop1\":\"test2\",\"prop2\":\"test22\"}]}", + line); } } diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/TechnicalServlet.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/TechnicalServlet.java index 7fa981b44..4137db09c 100644 --- a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/TechnicalServlet.java +++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/TechnicalServlet.java @@ -6,9 +6,9 @@ * 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 @@ -66,8 +66,8 @@ public class TechnicalServlet extends HttpServlet { } ODataHttpHandler handler = odata.createHandler(serviceMetadata); - handler.register(new TechnicalEntityProcessor(dataProvider)); - handler.register(new TechnicalPrimitiveComplexProcessor(dataProvider)); + handler.register(new TechnicalEntityProcessor(dataProvider, serviceMetadata)); + handler.register(new TechnicalPrimitiveComplexProcessor(dataProvider, serviceMetadata)); handler.register(new TechnicalBatchProcessor(dataProvider)); handler.process(req, resp); } catch (RuntimeException e) { diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/data/DataCreator.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/data/DataCreator.java index e6193dff5..1f28900f4 100644 --- a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/data/DataCreator.java +++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/data/DataCreator.java @@ -6,9 +6,9 @@ * 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 @@ -41,11 +41,14 @@ import org.apache.olingo.commons.core.data.EntityImpl; import org.apache.olingo.commons.core.data.EntitySetImpl; import org.apache.olingo.commons.core.data.LinkImpl; import org.apache.olingo.commons.core.data.PropertyImpl; +import org.apache.olingo.server.tecsvc.provider.ComplexTypeProvider; +import org.apache.olingo.server.tecsvc.provider.EntityTypeProvider; + public class DataCreator { private static final UUID GUID = UUID.fromString("01234567-89ab-cdef-0123-456789abcdef"); - + private static final String ctPropComp = ComplexTypeProvider.nameCTTwoPrim.getFullQualifiedNameAsString(); private final Map data; public DataCreator() { @@ -96,7 +99,9 @@ public class DataCreator { .addProperty(createPrimitive("PropertyInt16", i)) .addProperty(createPrimitive("PropertyString", "Number:" + i))); } - + for (Entity en:entitySet.getEntities()) { + en.setType(EntityTypeProvider.nameETServerSidePaging.getFullQualifiedNameAsString()); + } return entitySet; } @@ -106,7 +111,9 @@ public class DataCreator { entitySet.getEntities().add(createETKeyNavEntity(1, "I am String Property 1")); entitySet.getEntities().add(createETKeyNavEntity(2, "I am String Property 2")); entitySet.getEntities().add(createETKeyNavEntity(3, "I am String Property 3")); - + for (Entity en:entitySet.getEntities()) { + en.setType(EntityTypeProvider.nameETKeyNav.getFullQualifiedNameAsString()); + } return entitySet; } @@ -115,10 +122,12 @@ public class DataCreator { return new EntityImpl() .addProperty(createPrimitive("PropertyInt16", propertyInt16)) .addProperty(createPrimitive("PropertyString", propertyString)) - .addProperty(createComplex("PropertyCompNav", + .addProperty(createComplex("PropertyCompNav", ctPropComp, createPrimitive("PropertyInt16", 1))) .addProperty(createKeyNavAllPrimComplexValue("PropertyCompAllPrim")) - .addProperty(createComplex("PropertyCompTwoPrim", + .addProperty( + createComplex("PropertyCompTwoPrim", + ComplexTypeProvider.nameCTTwoPrim.getFullQualifiedNameAsString(), createPrimitive("PropertyInt16", 16), createPrimitive("PropertyString", "Test123"))) .addProperty(createPrimitiveCollection("CollPropertyString", @@ -126,7 +135,9 @@ public class DataCreator { "Employee2@company.example", "Employee3@company.example")) .addProperty(createPrimitiveCollection("CollPropertyInt16", 1000, 2000, 30112)) - .addProperty(createComplexCollection("CollPropertyComp", + .addProperty( + createComplexCollection("CollPropertyComp", ComplexTypeProvider.nameCTPrimComp + .getFullQualifiedNameAsString(), Arrays.asList( createPrimitive("PropertyInt16", 1), createKeyNavAllPrimComplexValue("PropertyComp")), @@ -136,9 +147,11 @@ public class DataCreator { Arrays.asList( createPrimitive("PropertyInt16", 3), createKeyNavAllPrimComplexValue("PropertyComp")))) - .addProperty(createComplex("PropertyCompCompNav", + .addProperty( + createComplex("PropertyCompCompNav", + ComplexTypeProvider.nameCTCompComp.getFullQualifiedNameAsString(), createPrimitive("PropertyString", "1"), - createComplex("PropertyComp", createPrimitive("PropertyInt16", 1)))); + createComplex("PropertyComp", ctPropComp, createPrimitive("PropertyInt16", 1)))); } private EntitySet createESTwoKeyNav() { @@ -148,7 +161,9 @@ public class DataCreator { entitySet.getEntities().add(createESTwoKeyNavEntity(1, "2")); entitySet.getEntities().add(createESTwoKeyNavEntity(2, "1")); entitySet.getEntities().add(createESTwoKeyNavEntity(3, "1")); - + for (Entity en:entitySet.getEntities()) { + en.setType(EntityTypeProvider.nameETTwoKeyNav.getFullQualifiedNameAsString()); + } return entitySet; } @@ -157,9 +172,9 @@ public class DataCreator { return new EntityImpl() .addProperty(createPrimitive("PropertyInt16", propertyInt16)) .addProperty(createPrimitive("PropertyString", propertyString)) - .addProperty(createComplex("PropertyComp", + .addProperty(createComplex("PropertyComp",ctPropComp, createPrimitive("PropertyInt16", 11), - createComplex("PropertyComp", + createComplex("PropertyComp", ctPropComp, createPrimitive("PropertyString", "StringValue"), createPrimitive("PropertyBinary", new byte[] { 1, 35, 69, 103, -119, -85, -51, -17 }), createPrimitive("PropertyBoolean", true), @@ -175,20 +190,26 @@ public class DataCreator { createPrimitive("PropertyInt64", Long.MAX_VALUE), createPrimitive("PropertySByte", Byte.MAX_VALUE), createPrimitive("PropertyTimeOfDay", getTime(21, 5, 59))))) - .addProperty(createComplex("PropertyCompNav", + .addProperty( + createComplex("PropertyCompNav", + ComplexTypeProvider.nameCTCompNav.getFullQualifiedNameAsString(), createPrimitive("PropertyInt16", 1), createKeyNavAllPrimComplexValue("PropertyComp"))) - .addProperty(createComplexCollection("CollPropertyComp")) - .addProperty(createComplexCollection("CollPropertyCompNav", + .addProperty(createComplexCollection("CollPropertyComp", null)) + .addProperty( + createComplexCollection("CollPropertyCompNav", + ComplexTypeProvider.nameCTCompNav.getFullQualifiedNameAsString(), Arrays.asList(createPrimitive("PropertyInt16", 1)))) .addProperty(createPrimitiveCollection("CollPropertyString", 1, 2)) - .addProperty(createComplex("PropertyCompTwoPrim", + .addProperty( + createComplex("PropertyCompTwoPrim", + ComplexTypeProvider.nameCTTwoPrim.getFullQualifiedNameAsString(), createPrimitive("PropertyInt16", 11), createPrimitive("PropertyString", "11"))); } private Property createKeyNavAllPrimComplexValue(final String name) { - return createComplex(name, + return createComplex(name, ComplexTypeProvider.nameCTAllPrim.getFullQualifiedNameAsString(), createPrimitive("PropertyString", "First Resource - positive values"), createPrimitive("PropertyBinary", new byte[] { 1, 35, 69, 103, -119, -85, -51, -17 }), createPrimitive("PropertyBoolean", true), @@ -213,8 +234,9 @@ public class DataCreator { entitySet.getEntities().add(new EntityImpl() .addProperty(createPrimitive("PropertyInt16", Short.MAX_VALUE)) - .addProperty(createComplex("PropertyComp", - createComplexCollection("CollPropertyComp", + .addProperty(createComplex("PropertyComp", null, + createComplexCollection("CollPropertyComp", ComplexTypeProvider.nameCTTwoPrim + .getFullQualifiedNameAsString(), Arrays.asList( createPrimitive("PropertyInt16", 555), createPrimitive("PropertyString", "1 Test Complex in Complex Property")), @@ -227,8 +249,9 @@ public class DataCreator { entitySet.getEntities().add(new EntityImpl() .addProperty(createPrimitive("PropertyInt16", 12345)) - .addProperty(createComplex("PropertyComp", - createComplexCollection("CollPropertyComp", + .addProperty(createComplex("PropertyComp",null, + createComplexCollection("CollPropertyComp", ComplexTypeProvider.nameCTTwoPrim + .getFullQualifiedNameAsString(), Arrays.asList( createPrimitive("PropertyInt16", 888), createPrimitive("PropertyString", "11 Test Complex in Complex Property")), @@ -238,7 +261,9 @@ public class DataCreator { Arrays.asList( createPrimitive("PropertyInt16", 0), createPrimitive("PropertyString", "13 Test Complex in Complex Property")))))); - + for (Entity en:entitySet.getEntities()) { + en.setType(EntityTypeProvider.nameETCompCollComp.getFullQualifiedNameAsString()); + } return entitySet; } @@ -260,7 +285,9 @@ public class DataCreator { entitySet.getEntities().add(new EntityImpl() .addProperty(createPrimitive("PropertyInt16", Short.MAX_VALUE)) .addProperty(createPrimitive("PropertyString", "Test String4"))); - + for (Entity en:entitySet.getEntities()) { + en.setType(EntityTypeProvider.nameETTwoPrim.getFullQualifiedNameAsString()); + } return entitySet; } @@ -322,7 +349,9 @@ public class DataCreator { .addProperty(createPrimitive("PropertyDuration", 0)) .addProperty(createPrimitive("PropertyGuid", UUID.fromString("76543201-23ab-cdef-0123-456789cccddd"))) .addProperty(createPrimitive("PropertyTimeOfDay", getTime(0, 1, 1)))); - + for (Entity en:entitySet.getEntities()) { + en.setType(EntityTypeProvider.nameETAllPrim.getFullQualifiedNameAsString()); + } return entitySet; } @@ -331,7 +360,7 @@ public class DataCreator { Entity entity = new EntityImpl(); entity.addProperty(createPrimitive("PropertyInt16", Short.MAX_VALUE)); - entity.addProperty(createComplex("PropertyComp", + entity.addProperty(createComplex("PropertyComp",ctPropComp, createPrimitive("PropertyString", "First Resource - first"), createPrimitive("PropertyBinary", new byte[] { 0x01, 0x23, 0x45, 0x67, (byte) 0x89, (byte) 0xAB, (byte) 0xCD, (byte) 0xEF }), @@ -353,7 +382,7 @@ public class DataCreator { entity = new EntityImpl(); entity.addProperty(createPrimitive("PropertyInt16", 7)); - entity.addProperty(createComplex("PropertyComp", + entity.addProperty(createComplex("PropertyComp",ctPropComp, createPrimitive("PropertyString", "Second Resource - second"), createPrimitive("PropertyBinary", new byte[] { 0x01, 0x23, 0x45, 0x67, (byte) 0x89, (byte) 0xAB, (byte) 0xCD, (byte) 0xEF }), @@ -375,7 +404,7 @@ public class DataCreator { entity = new EntityImpl(); entity.addProperty(createPrimitive("PropertyInt16", 0)); - entity.addProperty(createComplex("PropertyComp", + entity.addProperty(createComplex("PropertyComp",ctPropComp, createPrimitive("PropertyString", "Third Resource - third"), createPrimitive("PropertyBinary", new byte[] { 0x01, 0x23, 0x45, 0x67, (byte) 0x89, (byte) 0xAB, (byte) 0xCD, (byte) 0xEF }), @@ -394,7 +423,9 @@ public class DataCreator { createPrimitive("PropertySByte", Byte.MAX_VALUE), createPrimitive("PropertyTimeOfDay", getTime(13, 27, 45)))); entitySet.getEntities().add(entity); - + for (Entity en:entitySet.getEntities()) { + en.setType(EntityTypeProvider.nameETCompAllPrim.getFullQualifiedNameAsString()); + } return entitySet; } @@ -444,13 +475,15 @@ public class DataCreator { entity.getProperties().addAll(entitySet.getEntities().get(0).getProperties()); entity.getProperties().set(0, createPrimitive("PropertyInt16", 3)); entitySet.getEntities().add(entity); - + for (Entity en:entitySet.getEntities()) { + en.setType(EntityTypeProvider.nameETCollAllPrim.getFullQualifiedNameAsString()); + } return entitySet; } private EntitySet createESMixPrimCollComp() { @SuppressWarnings("unchecked") - final Property complexCollection = createComplexCollection("CollPropertyComp", + final Property complexCollection = createComplexCollection("CollPropertyComp", ctPropComp, Arrays.asList(createPrimitive("PropertyInt16", 123), createPrimitive("PropertyString", "TEST 1")), Arrays.asList(createPrimitive("PropertyInt16", 456), createPrimitive("PropertyString", "TEST 2")), Arrays.asList(createPrimitive("PropertyInt16", 789), createPrimitive("PropertyString", "TEST 3"))); @@ -461,7 +494,7 @@ public class DataCreator { .addProperty(createPrimitive("PropertyInt16", Short.MAX_VALUE)) .addProperty(createPrimitiveCollection("CollPropertyString", "Employee1@company.example", "Employee2@company.example", "Employee3@company.example")) - .addProperty(createComplex("PropertyComp", + .addProperty(createComplex("PropertyComp",ctPropComp, createPrimitive("PropertyInt16", 111), createPrimitive("PropertyString", "TEST A"))) .addProperty(complexCollection)); @@ -470,7 +503,7 @@ public class DataCreator { .addProperty(createPrimitive("PropertyInt16", 7)) .addProperty(createPrimitiveCollection("CollPropertyString", "Employee1@company.example", "Employee2@company.example", "Employee3@company.example")) - .addProperty(createComplex("PropertyComp", + .addProperty(createComplex("PropertyComp",ctPropComp, createPrimitive("PropertyInt16", 222), createPrimitive("PropertyString", "TEST B"))) .addProperty(complexCollection)); @@ -479,11 +512,13 @@ public class DataCreator { .addProperty(createPrimitive("PropertyInt16", 0)) .addProperty(createPrimitiveCollection("CollPropertyString", "Employee1@company.example", "Employee2@company.example", "Employee3@company.example")) - .addProperty(createComplex("PropertyComp", + .addProperty(createComplex("PropertyComp",ctPropComp, createPrimitive("PropertyInt16", 333), createPrimitive("PropertyString", "TEST C"))) .addProperty(complexCollection)); - + for (Entity en:entitySet.getEntities()) { + en.setType(EntityTypeProvider.nameETMixPrimCollComp.getFullQualifiedNameAsString()); + } return entitySet; } @@ -519,7 +554,9 @@ public class DataCreator { .addProperty(createPrimitive("PropertyDuration", 6)) .addProperty(createPrimitive("PropertyGuid", GUID)) .addProperty(createPrimitive("PropertyTimeOfDay", getTime(2, 48, 21)))); - + for (Entity en:entitySet.getEntities()) { + en.setType(EntityTypeProvider.nameETAllKey.getFullQualifiedNameAsString()); + } return entitySet; } @@ -528,20 +565,22 @@ public class DataCreator { Entity entity = new EntityImpl(); entity.addProperty(createPrimitive("PropertyInt16", 1)); - entity.addProperty(createComplex("PropertyComp", - createComplex("PropertyComp", + entity.addProperty(createComplex("PropertyComp", null, + createComplex("PropertyComp",ctPropComp, createPrimitive("PropertyInt16", 123), createPrimitive("PropertyString", "String 1")))); entitySet.getEntities().add(entity); entity = new EntityImpl(); entity.addProperty(createPrimitive("PropertyInt16", 2)); - entity.addProperty(createComplex("PropertyComp", - createComplex("PropertyComp", + entity.addProperty(createComplex("PropertyComp", null, + createComplex("PropertyComp",ctPropComp, createPrimitive("PropertyInt16", 987), createPrimitive("PropertyString", "String 2")))); entitySet.getEntities().add(entity); - + for (Entity en:entitySet.getEntities()) { + en.setType(EntityTypeProvider.nameETCompComp.getFullQualifiedNameAsString()); + } return entitySet; } @@ -571,7 +610,9 @@ public class DataCreator { .addProperty(createPrimitive(DataProvider.MEDIA_PROPERTY_NAME, createImage("black"))); entity.setMediaContentType("image/svg+xml"); entitySet.getEntities().add(entity); - + for (Entity en:entitySet.getEntities()) { + en.setType(EntityTypeProvider.nameETMedia.getFullQualifiedNameAsString()); + } return entitySet; } @@ -677,22 +718,23 @@ public class DataCreator { return new PropertyImpl(null, name, ValueType.COLLECTION_PRIMITIVE, Arrays.asList(values)); } - protected static Property createComplex(final String name, final Property... properties) { + protected static Property createComplex(final String name, String type, final Property... properties) { ComplexValue complexValue = new ComplexValueImpl(); for (final Property property : properties) { complexValue.getValue().add(property); } - return new PropertyImpl(null, name, ValueType.COMPLEX, complexValue); + return new PropertyImpl(type, name, ValueType.COMPLEX, complexValue); } - protected static Property createComplexCollection(final String name, final List... propertiesList) { + protected static Property createComplexCollection(final String name, String type, + final List... propertiesList) { List complexCollection = new ArrayList(); for (final List properties : propertiesList) { ComplexValue complexValue = new ComplexValueImpl(); complexValue.getValue().addAll(properties); complexCollection.add(complexValue); } - return new PropertyImpl(null, name, ValueType.COLLECTION_COMPLEX, complexCollection); + return new PropertyImpl(type, name, ValueType.COLLECTION_COMPLEX, complexCollection); } private Calendar getDateTime(final int year, final int month, final int day, diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/data/DataProvider.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/data/DataProvider.java index fed499fef..4fc9300e6 100644 --- a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/data/DataProvider.java +++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/data/DataProvider.java @@ -6,9 +6,9 @@ * 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 @@ -138,7 +138,7 @@ public class DataProvider { final List entities = entitySet.getEntities(); final Map newKey = findFreeComposedKey(entities, edmEntitySet.getEntityType()); final Entity newEntity = new EntityImpl(); - + newEntity.setType(edmEntityType.getFullQualifiedName().getFullQualifiedNameAsString()); for (final String keyName : edmEntityType.getKeyPredicateNames()) { newEntity.addProperty(DataCreator.createPrimitive(keyName, newKey.get(keyName))); } @@ -194,7 +194,8 @@ public class DataProvider { return true; } - private void createProperties(final EdmStructuredType type, List properties) throws DataProviderException { + private void createProperties(final EdmStructuredType type, List properties) + throws DataProviderException { final List keyNames = type instanceof EdmEntityType ? ((EdmEntityType) type).getKeyPredicateNames() : Collections. emptyList(); for (final String propertyName : type.getPropertyNames()) { @@ -204,11 +205,11 @@ public class DataProvider { } } } - - private Property createProperty(final EdmProperty edmProperty, final String propertyName) + + private Property createProperty(final EdmProperty edmProperty, final String propertyName) throws DataProviderException { Property newProperty; - + if (edmProperty.isPrimitive()) { newProperty = edmProperty.isCollection() ? DataCreator.createPrimitiveCollection(propertyName) : @@ -216,17 +217,19 @@ public class DataProvider { } else { if (edmProperty.isCollection()) { @SuppressWarnings("unchecked") - Property newProperty2 = DataCreator.createComplexCollection(propertyName); + Property newProperty2 = DataCreator.createComplexCollection(propertyName, edmProperty + .getType().getFullQualifiedName().getFullQualifiedNameAsString()); newProperty = newProperty2; } else { - newProperty = DataCreator.createComplex(propertyName); + newProperty = DataCreator.createComplex(propertyName, edmProperty.getType() + .getFullQualifiedName().getFullQualifiedNameAsString()); createProperties((EdmComplexType) edmProperty.getType(), newProperty.asComplex().getValue()); } } - + return newProperty; } - + public void update(final String rawBaseUri, final EdmEntitySet edmEntitySet, Entity entity, final Entity changedEntity, final boolean patch, final boolean isInsert) throws DataProviderException { @@ -433,7 +436,7 @@ public class DataProvider { } } - private ComplexValue createComplexValue(final EdmProperty edmProperty, final ComplexValue complexValue, + private ComplexValue createComplexValue(final EdmProperty edmProperty, final ComplexValue complexValue, final boolean patch) throws DataProviderException { final ComplexValueImpl result = new ComplexValueImpl(); final EdmComplexType edmType = (EdmComplexType) edmProperty.getType(); @@ -445,7 +448,7 @@ public class DataProvider { final Property currentProperty = findProperty(propertyName, givenProperties); final Property newProperty = createProperty(innerEdmProperty, propertyName); result.getValue().add(newProperty); - + if (currentProperty != null) { updateProperty(innerEdmProperty, newProperty, currentProperty, patch); } else { @@ -459,7 +462,7 @@ public class DataProvider { } } } - + return result; } diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/data/FunctionData.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/data/FunctionData.java index 5451d5dd4..316a7b0d0 100644 --- a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/data/FunctionData.java +++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/data/FunctionData.java @@ -31,6 +31,7 @@ import org.apache.olingo.commons.core.data.EntitySetImpl; import org.apache.olingo.commons.core.edm.primitivetype.EdmPrimitiveTypeFactory; import org.apache.olingo.server.api.uri.UriParameter; import org.apache.olingo.server.tecsvc.data.DataProvider.DataProviderException; +import org.apache.olingo.server.tecsvc.provider.ComplexTypeProvider; public class FunctionData { @@ -80,12 +81,12 @@ public class FunctionData { } else if (name.equals("UFCRTCollString")) { return data.get("ESCollAllPrim").getEntities().get(0).getProperty("CollPropertyString"); } else if (name.equals("UFCRTCTTwoPrim")) { - return DataCreator.createComplex(name, + return DataCreator.createComplex(name, ComplexTypeProvider.nameCTTwoPrim.getFullQualifiedNameAsString(), DataCreator.createPrimitive("PropertyInt16", 16), DataCreator.createPrimitive("PropertyString", "UFCRTCTTwoPrim string value")); } else if (name.equals("UFCRTCTTwoPrimParam")) { try { - return DataCreator.createComplex(name, + return DataCreator.createComplex(name,ComplexTypeProvider.nameCTTwoPrim.getFullQualifiedNameAsString(), DataCreator.createPrimitive("PropertyInt16", EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Int16).valueOfString( getParameterText("ParameterInt16", parameters), @@ -99,7 +100,7 @@ public class FunctionData { throw new DataProviderException("Error in function " + name + ".", e); } } else if (name.equals("UFCRTCollCTTwoPrim")) { - return DataCreator.createComplexCollection(name, + return DataCreator.createComplexCollection(name,ComplexTypeProvider.nameCTTwoPrim.getFullQualifiedNameAsString(), Arrays.asList(DataCreator.createPrimitive("PropertyInt16", 16), DataCreator.createPrimitive("PropertyString", "Test123")), Arrays.asList(DataCreator.createPrimitive("PropertyInt16", 17), diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalEntityProcessor.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalEntityProcessor.java index f610fc2bb..841eabe11 100644 --- a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalEntityProcessor.java +++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalEntityProcessor.java @@ -6,9 +6,9 @@ * 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 @@ -38,6 +38,7 @@ import org.apache.olingo.commons.core.data.EntitySetImpl; import org.apache.olingo.server.api.ODataApplicationException; import org.apache.olingo.server.api.ODataRequest; import org.apache.olingo.server.api.ODataResponse; +import org.apache.olingo.server.api.ServiceMetadata; import org.apache.olingo.server.api.deserializer.DeserializerException; import org.apache.olingo.server.api.deserializer.ODataDeserializer; import org.apache.olingo.server.api.processor.ActionEntityCollectionProcessor; @@ -74,8 +75,11 @@ public class TechnicalEntityProcessor extends TechnicalProcessor EntityProcessor, ActionEntityProcessor, MediaEntityProcessor, ActionVoidProcessor { - public TechnicalEntityProcessor(final DataProvider dataProvider) { + private final ServiceMetadata serviceMetadata; + + public TechnicalEntityProcessor(final DataProvider dataProvider, ServiceMetadata serviceMetadata) { super(dataProvider); + this.serviceMetadata = serviceMetadata; } @Override @@ -109,21 +113,24 @@ public class TechnicalEntityProcessor extends TechnicalProcessor entitySet, edmEntitySet, request.getRawRequestUri()); - + // Apply expand system query option final ODataFormat format = ODataFormat.fromContentType(requestedContentType); ODataSerializer serializer = odata.createSerializer(format); final ExpandOption expand = uriInfo.getExpandOption(); final SelectOption select = uriInfo.getSelectOption(); - + // Create a shallow copy of each entity. So the expanded navigation properties can be modified for serialization, // without affecting the data stored in the database. final ExpandSystemQueryOptionHandler expandHandler = new ExpandSystemQueryOptionHandler(); final EntitySet entitySetSerialization = expandHandler.copyEntitySetShallowRekursive(entitySet); expandHandler.applyExpandQueryOptions(entitySetSerialization, edmEntitySet, expand); - + // Serialize - response.setContent(serializer.entityCollection(edmEntityType, entitySetSerialization, + response.setContent(serializer.entityCollection( + this.serviceMetadata, + edmEntityType, + entitySetSerialization, EntityCollectionSerializerOptions.with() .contextURL(format == ODataFormat.JSON_NO_METADATA ? null : getContextUrl(edmEntitySet, edmEntityType, false, expand, select)) @@ -170,17 +177,20 @@ public class TechnicalEntityProcessor extends TechnicalProcessor edmEntitySet.getEntityType(); final Entity entity = readEntity(uriInfo); - + final ODataFormat format = ODataFormat.fromContentType(requestedContentType); ODataSerializer serializer = odata.createSerializer(format); final ExpandOption expand = uriInfo.getExpandOption(); final SelectOption select = uriInfo.getSelectOption(); - + final ExpandSystemQueryOptionHandler expandHandler = new ExpandSystemQueryOptionHandler(); final Entity entitySerialization = expandHandler.copyEntityShallowRekursive(entity); expandHandler.applyExpandQueryOptions(entitySerialization, edmEntitySet, expand); - - response.setContent(serializer.entity(edmEntitySet.getEntityType(), entitySerialization, + + response.setContent(serializer.entity( + this.serviceMetadata, + edmEntitySet.getEntityType(), + entitySerialization, EntitySerializerOptions.with() .contextURL(format == ODataFormat.JSON_NO_METADATA ? null : getContextUrl(edmEntitySet, edmEntityType, true, expand, select)) @@ -233,7 +243,7 @@ public class TechnicalEntityProcessor extends TechnicalProcessor final ODataFormat format = ODataFormat.fromContentType(responseFormat); ODataSerializer serializer = odata.createSerializer(format); - response.setContent(serializer.entity(edmEntityType, entity, + response.setContent(serializer.entity(this.serviceMetadata, edmEntityType, entity, EntitySerializerOptions.with() .contextURL(format == ODataFormat.JSON_NO_METADATA ? null : getContextUrl(edmEntitySet, edmEntityType, true, null, null)) diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalPrimitiveComplexProcessor.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalPrimitiveComplexProcessor.java index e36dc6b7c..b853e488f 100644 --- a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalPrimitiveComplexProcessor.java +++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalPrimitiveComplexProcessor.java @@ -6,9 +6,9 @@ * 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 @@ -44,6 +44,7 @@ import org.apache.olingo.commons.core.edm.primitivetype.EdmPrimitiveTypeFactory; import org.apache.olingo.server.api.ODataApplicationException; import org.apache.olingo.server.api.ODataRequest; import org.apache.olingo.server.api.ODataResponse; +import org.apache.olingo.server.api.ServiceMetadata; import org.apache.olingo.server.api.deserializer.DeserializerException; import org.apache.olingo.server.api.processor.ActionComplexCollectionProcessor; import org.apache.olingo.server.api.processor.ActionComplexProcessor; @@ -81,8 +82,12 @@ public class TechnicalPrimitiveComplexProcessor extends TechnicalProcessor ComplexProcessor, ActionComplexProcessor, ComplexCollectionProcessor, ActionComplexCollectionProcessor { - public TechnicalPrimitiveComplexProcessor(final DataProvider dataProvider) { + private final ServiceMetadata serviceMetadata; + + public TechnicalPrimitiveComplexProcessor(final DataProvider dataProvider, + ServiceMetadata serviceMetadata) { super(dataProvider); + this.serviceMetadata = serviceMetadata; } @Override @@ -246,7 +251,7 @@ public class TechnicalPrimitiveComplexProcessor extends TechnicalProcessor .build())); break; case COMPLEX: - response.setContent(serializer.complex((EdmComplexType) type, property, + response.setContent(serializer.complex(this.serviceMetadata,(EdmComplexType) type, property, ComplexSerializerOptions.with().contextURL(contextURL) .expand(expand).select(select) .build())); @@ -262,7 +267,7 @@ public class TechnicalPrimitiveComplexProcessor extends TechnicalProcessor .build())); break; case COLLECTION_COMPLEX: - response.setContent(serializer.complexCollection((EdmComplexType) type, property, + response.setContent(serializer.complexCollection(this.serviceMetadata, (EdmComplexType) type, property, ComplexSerializerOptions.with().contextURL(contextURL) .expand(expand).select(select) .build())); diff --git a/lib/server-tecsvc/src/test/java/org/apache/olingo/server/tecsvc/data/DataProviderTest.java b/lib/server-tecsvc/src/test/java/org/apache/olingo/server/tecsvc/data/DataProviderTest.java index 76edb3333..4549a0349 100644 --- a/lib/server-tecsvc/src/test/java/org/apache/olingo/server/tecsvc/data/DataProviderTest.java +++ b/lib/server-tecsvc/src/test/java/org/apache/olingo/server/tecsvc/data/DataProviderTest.java @@ -22,9 +22,9 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import org.apache.olingo.commons.api.data.ComplexValue; import org.apache.olingo.commons.api.data.Entity; import org.apache.olingo.commons.api.data.EntitySet; -import org.apache.olingo.commons.api.data.ComplexValue; import org.apache.olingo.commons.api.data.Property; import org.apache.olingo.commons.api.edm.Edm; import org.apache.olingo.commons.api.edm.EdmEntityContainer; diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializerEntityTest.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializerEntityTest.java index cd421f43e..870f7c3bb 100644 --- a/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializerEntityTest.java +++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializerEntityTest.java @@ -6,9 +6,9 @@ * 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 diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializerTest.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializerTest.java index 038c6686b..ee3868473 100644 --- a/lib/server-test/src/test/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializerTest.java +++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializerTest.java @@ -41,11 +41,12 @@ import org.apache.olingo.commons.api.edm.FullQualifiedName; import org.apache.olingo.commons.api.format.ODataFormat; import org.apache.olingo.commons.core.data.PropertyImpl; import org.apache.olingo.server.api.OData; +import org.apache.olingo.server.api.ServiceMetadata; import org.apache.olingo.server.api.edmx.EdmxReference; import org.apache.olingo.server.api.serializer.ComplexSerializerOptions; import org.apache.olingo.server.api.serializer.EntityCollectionSerializerOptions; -import org.apache.olingo.server.api.serializer.ODataSerializer; import org.apache.olingo.server.api.serializer.EntitySerializerOptions; +import org.apache.olingo.server.api.serializer.ODataSerializer; import org.apache.olingo.server.api.serializer.PrimitiveSerializerOptions; import org.apache.olingo.server.api.serializer.SerializerException; import org.apache.olingo.server.api.uri.UriHelper; @@ -64,9 +65,9 @@ import org.junit.Test; import org.mockito.Mockito; public class ODataJsonSerializerTest { - - private static final Edm edm = OData.newInstance().createServiceMetadata( - new EdmTechProvider(), Collections. emptyList()).getEdm(); + private static final ServiceMetadata metadata = OData.newInstance().createServiceMetadata( + new EdmTechProvider(), Collections. emptyList()); + private static final Edm edm = metadata.getEdm(); private static final EdmEntityContainer entityContainer = edm.getEntityContainer( new FullQualifiedName("olingo.odata.test1", "Container")); private final DataProvider data = new DataProvider(); @@ -77,7 +78,7 @@ public class ODataJsonSerializerTest { public void entitySimple() throws Exception { final EdmEntitySet edmEntitySet = entityContainer.getEntitySet("ESAllPrim"); final Entity entity = data.readAll(edmEntitySet).getEntities().get(0); - InputStream result = serializer.entity(edmEntitySet.getEntityType(), entity, + InputStream result = serializer.entity(metadata, edmEntitySet.getEntityType(), entity, EntitySerializerOptions.with() .contextURL(ContextURL.with().entitySet(edmEntitySet).suffix(Suffix.ENTITY).build()) .build()); @@ -109,7 +110,8 @@ public class ODataJsonSerializerTest { final EdmEntitySet edmEntitySet = entityContainer.getEntitySet("ESAllPrim"); Entity entity = data.readAll(edmEntitySet).getEntities().get(0); entity.getProperties().retainAll(Arrays.asList(entity.getProperties().get(0))); - final String resultString = IOUtils.toString(serializer.entity(edmEntitySet.getEntityType(), entity, + final String resultString = IOUtils.toString(serializer.entity(metadata, edmEntitySet.getEntityType(), + entity, EntitySerializerOptions.with() .contextURL(ContextURL.with().entitySet(edmEntitySet).suffix(Suffix.ENTITY).build()) .build())); @@ -130,7 +132,7 @@ public class ODataJsonSerializerTest { final EdmEntitySet edmEntitySet = entityContainer.getEntitySet("ESAllPrim"); Entity entity = data.readAll(edmEntitySet).getEntities().get(0); entity.getProperties().clear(); - serializer.entity(edmEntitySet.getEntityType(), entity, + serializer.entity(metadata, edmEntitySet.getEntityType(), entity, EntitySerializerOptions.with() .contextURL(ContextURL.with().entitySet(edmEntitySet).suffix(Suffix.ENTITY).build()) .build()); @@ -142,7 +144,7 @@ public class ODataJsonSerializerTest { Entity entity = data.readAll(edmEntitySet).getEntities().get(0); entity.getProperties().get(0).setValue(ValueType.PRIMITIVE, false); try { - serializer.entity(edmEntitySet.getEntityType(), entity, + serializer.entity(metadata, edmEntitySet.getEntityType(), entity, EntitySerializerOptions.with() .contextURL(ContextURL.with().entitySet(edmEntitySet).suffix(Suffix.ENTITY).build()) .build()); @@ -163,7 +165,7 @@ public class ODataJsonSerializerTest { entitySet.setNext(URI.create("/next")); CountOption countOption = Mockito.mock(CountOption.class); Mockito.when(countOption.getValue()).thenReturn(true); - InputStream result = serializer.entityCollection(edmEntitySet.getEntityType(), entitySet, + InputStream result = serializer.entityCollection(metadata, edmEntitySet.getEntityType(), entitySet, EntityCollectionSerializerOptions.with() .contextURL(ContextURL.with().entitySet(edmEntitySet).build()) .count(countOption) @@ -188,7 +190,7 @@ public class ODataJsonSerializerTest { public void entityCollAllPrim() throws Exception { final EdmEntitySet edmEntitySet = entityContainer.getEntitySet("ESCollAllPrim"); final Entity entity = data.readAll(edmEntitySet).getEntities().get(0); - InputStream result = serializer.entity(edmEntitySet.getEntityType(), entity, + InputStream result = serializer.entity(metadata, edmEntitySet.getEntityType(), entity, EntitySerializerOptions.with() .contextURL(ContextURL.with().serviceRoot(URI.create("http://host/service/")) .entitySet(edmEntitySet).suffix(Suffix.ENTITY).build()) @@ -224,7 +226,7 @@ public class ODataJsonSerializerTest { public void entityCompAllPrim() throws Exception { final EdmEntitySet edmEntitySet = entityContainer.getEntitySet("ESCompAllPrim"); final Entity entity = data.readAll(edmEntitySet).getEntities().get(0); - InputStream result = serializer.entity(edmEntitySet.getEntityType(), entity, + InputStream result = serializer.entity(metadata, edmEntitySet.getEntityType(), entity, EntitySerializerOptions.with() .contextURL(ContextURL.with().entitySet(edmEntitySet).suffix(Suffix.ENTITY).build()) .build()); @@ -257,7 +259,7 @@ public class ODataJsonSerializerTest { public void entityMixPrimCollComp() throws Exception { final EdmEntitySet edmEntitySet = entityContainer.getEntitySet("ESMixPrimCollComp"); final Entity entity = data.readAll(edmEntitySet).getEntities().get(0); - InputStream result = serializer.entity(edmEntitySet.getEntityType(), entity, + InputStream result = serializer.entity(metadata, edmEntitySet.getEntityType(), entity, EntitySerializerOptions.with() .contextURL(ContextURL.with().entitySet(edmEntitySet).suffix(Suffix.ENTITY).build()) .build()); @@ -280,7 +282,7 @@ public class ODataJsonSerializerTest { final EdmEntitySet edmEntitySet = entityContainer.getEntitySet("ESMixPrimCollComp"); Entity entity = data.readAll(edmEntitySet).getEntities().get(0); entity.getProperties().retainAll(Arrays.asList(entity.getProperties().get(0))); - final String resultString = IOUtils.toString(serializer.entity(edmEntitySet.getEntityType(), entity, + final String resultString = IOUtils.toString(serializer.entity(metadata, edmEntitySet.getEntityType(), entity, EntitySerializerOptions.with() .contextURL(ContextURL.with().entitySet(edmEntitySet).suffix(Suffix.ENTITY).build()) .build())); @@ -295,7 +297,7 @@ public class ODataJsonSerializerTest { final EdmEntitySet edmEntitySet = entityContainer.getEntitySet("ESTwoPrim"); final Entity entity = data.readAll(edmEntitySet).getEntities().get(0); InputStream result = new ODataJsonSerializer(ODataFormat.JSON_NO_METADATA) - .entity(edmEntitySet.getEntityType(), entity, null); + .entity(metadata, edmEntitySet.getEntityType(), entity, null); final String resultString = IOUtils.toString(result); final String expectedResult = "{\"PropertyInt16\":32766,\"PropertyString\":\"Test String1\"}"; Assert.assertEquals(expectedResult, resultString); @@ -306,7 +308,7 @@ public class ODataJsonSerializerTest { final EdmEntitySet edmEntitySet = entityContainer.getEntitySet("ESTwoPrim"); final EntitySet entitySet = data.readAll(edmEntitySet); InputStream result = new ODataJsonSerializer(ODataFormat.JSON_NO_METADATA) - .entityCollection(edmEntitySet.getEntityType(), entitySet, + .entityCollection(metadata, edmEntitySet.getEntityType(), entitySet, EntityCollectionSerializerOptions.with() .contextURL(ContextURL.with().entitySet(edmEntitySet).build()).build()); final String resultString = IOUtils.toString(result); @@ -323,7 +325,8 @@ public class ODataJsonSerializerTest { final EdmEntitySet edmEntitySet = entityContainer.getEntitySet("ESMedia"); Entity entity = data.readAll(edmEntitySet).getEntities().get(0); entity.setMediaETag("theMediaETag"); - final String resultString = IOUtils.toString(serializer.entity(edmEntitySet.getEntityType(), entity, + final String resultString = IOUtils.toString(serializer.entity(metadata, edmEntitySet.getEntityType(), + entity, EntitySerializerOptions.with() .contextURL(ContextURL.with().entitySet(edmEntitySet).suffix(Suffix.ENTITY).build()) .build())); @@ -337,7 +340,8 @@ public class ODataJsonSerializerTest { public void entitySetMedia() throws Exception { final EdmEntitySet edmEntitySet = entityContainer.getEntitySet("ESMedia"); final EntitySet entitySet = data.readAll(edmEntitySet); - final String resultString = IOUtils.toString(serializer.entityCollection(edmEntitySet.getEntityType(), entitySet, + final String resultString = IOUtils.toString(serializer.entityCollection(metadata, + edmEntitySet.getEntityType(), entitySet, EntityCollectionSerializerOptions.with() .contextURL(ContextURL.with().entitySet(edmEntitySet).build()).build())); final String expectedResult = "{\"@odata.context\":\"$metadata#ESMedia\",\"value\":[" @@ -358,7 +362,7 @@ public class ODataJsonSerializerTest { final SelectOption select = ExpandSelectMock.mockSelectOption(Arrays.asList( selectItem1, selectItem2, selectItem2)); InputStream result = serializer - .entity(entityType, entity, + .entity(metadata, entityType, entity, EntitySerializerOptions.with() .contextURL(ContextURL.with().entitySet(edmEntitySet) .selectList(helper.buildContextURLSelectList(entityType, null, select)) @@ -380,7 +384,7 @@ public class ODataJsonSerializerTest { SelectItem selectItem2 = Mockito.mock(SelectItem.class); Mockito.when(selectItem2.isStar()).thenReturn(true); final SelectOption select = ExpandSelectMock.mockSelectOption(Arrays.asList(selectItem1, selectItem2)); - InputStream result = serializer.entity(edmEntitySet.getEntityType(), entity, + InputStream result = serializer.entity(metadata, edmEntitySet.getEntityType(), entity, EntitySerializerOptions.with() .contextURL(ContextURL.with().entitySet(edmEntitySet).suffix(Suffix.ENTITY).build()) .select(select) @@ -399,7 +403,7 @@ public class ODataJsonSerializerTest { final SelectOption select = ExpandSelectMock.mockSelectOption(Arrays.asList( ExpandSelectMock.mockSelectItem(edmEntitySet, "PropertyComp", "PropertyComp", "PropertyString"))); InputStream result = serializer - .entityCollection(entityType, entitySet, + .entityCollection(metadata, entityType, entitySet, EntityCollectionSerializerOptions.with() .contextURL(ContextURL.with().entitySet(edmEntitySet) .selectList(helper.buildContextURLSelectList(entityType, null, select)) @@ -424,7 +428,7 @@ public class ODataJsonSerializerTest { ExpandSelectMock.mockSelectItem(edmEntitySet, "PropertyComp", "PropertyComp", "PropertyString"), ExpandSelectMock.mockSelectItem(edmEntitySet, "PropertyComp", "PropertyComp"))); final String resultString = IOUtils.toString(serializer - .entityCollection(entityType, entitySet, + .entityCollection(metadata, entityType, entitySet, EntityCollectionSerializerOptions.with() .contextURL(ContextURL.with().entitySet(edmEntitySet) .selectList(helper.buildContextURLSelectList(entityType, null, select)) @@ -445,7 +449,7 @@ public class ODataJsonSerializerTest { final Entity entity = data.readAll(edmEntitySet).getEntities().get(3); final ExpandOption expand = ExpandSelectMock.mockExpandOption(Arrays.asList( ExpandSelectMock.mockExpandItem(edmEntitySet, "NavPropertyETAllPrimOne"))); - InputStream result = serializer.entity(edmEntitySet.getEntityType(), entity, + InputStream result = serializer.entity(metadata, edmEntitySet.getEntityType(), entity, EntitySerializerOptions.with() .contextURL(ContextURL.with().entitySet(edmEntitySet).suffix(Suffix.ENTITY).build()) .expand(expand) @@ -484,7 +488,7 @@ public class ODataJsonSerializerTest { Mockito.when(expandItem.getSelectOption()).thenReturn(select); final ExpandOption expand = ExpandSelectMock.mockExpandOption(Arrays.asList(expandItem)); final String resultString = IOUtils.toString(serializer - .entity(entityType, entity, + .entity(metadata, entityType, entity, EntitySerializerOptions.with() .contextURL(ContextURL.with().entitySet(edmEntitySet) .selectList(helper.buildContextURLSelectList(entityType, expand, select)) @@ -511,7 +515,7 @@ public class ODataJsonSerializerTest { final SelectOption select = ExpandSelectMock.mockSelectOption(Arrays.asList( ExpandSelectMock.mockSelectItem(edmEntitySet, "PropertySByte"))); final String resultString = IOUtils.toString(serializer - .entity(entityType, entity, + .entity(metadata, entityType, entity, EntitySerializerOptions.with() .contextURL(ContextURL.with().entitySet(edmEntitySet) .selectList(helper.buildContextURLSelectList(entityType, expand, select)) @@ -538,7 +542,7 @@ public class ODataJsonSerializerTest { final SelectOption select = ExpandSelectMock.mockSelectOption(Arrays.asList( ExpandSelectMock.mockSelectItem(edmEntitySet, "PropertyTimeOfDay"))); final String resultString = IOUtils.toString(serializer - .entity(entityType, entity, + .entity(metadata, entityType, entity, EntitySerializerOptions.with() .contextURL(ContextURL.with().entitySet(edmEntitySet) .selectList(helper.buildContextURLSelectList(entityType, expand, select)) @@ -569,7 +573,7 @@ public class ODataJsonSerializerTest { Mockito.when(expandItemFirst.getSelectOption()).thenReturn(select); final ExpandOption expand = ExpandSelectMock.mockExpandOption(Arrays.asList(expandItemFirst)); final String resultString = IOUtils.toString(serializer - .entity(entityType, entity, + .entity(metadata, entityType, entity, EntitySerializerOptions.with() .contextURL(ContextURL.with().entitySet(edmEntitySet) .selectList(helper.buildContextURLSelectList(entityType, expand, select)) @@ -646,7 +650,7 @@ public class ODataJsonSerializerTest { final Property property = data.readAll(edmEntitySet).getEntities().get(0).getProperty("PropertyComp"); final String resultString = IOUtils.toString(serializer - .complex((EdmComplexType) edmProperty.getType(), property, + .complex(metadata, (EdmComplexType) edmProperty.getType(), property, ComplexSerializerOptions.with() .contextURL(ContextURL.with() .entitySet(edmEntitySet).keyPath("32767").navOrPropertyPath(edmProperty.getName()) @@ -665,7 +669,7 @@ public class ODataJsonSerializerTest { final Property property = data.readAll(edmEntitySet).getEntities().get(0).getProperty(edmProperty.getName()); final String resultString = IOUtils.toString(serializer - .complexCollection((EdmComplexType) edmProperty.getType(), property, + .complexCollection(metadata, (EdmComplexType) edmProperty.getType(), property, ComplexSerializerOptions.with() .contextURL(ContextURL.with() .entitySet(edmEntitySet).keyPath("32767").navOrPropertyPath(edmProperty.getName()) diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/antlr/TestUriParserImpl.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/antlr/TestUriParserImpl.java index 798c5c0a5..86cbf0eb1 100644 --- a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/antlr/TestUriParserImpl.java +++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/antlr/TestUriParserImpl.java @@ -6,9 +6,9 @@ * 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 @@ -1064,13 +1064,16 @@ public class TestUriParserImpl { public void testAlias() throws Exception { testUri.run("ESAllPrim", "$filter=PropertyInt16 eq @p1&@p1=1)") .goFilter().is("< eq <@p1>>"); - } - + } + @Test public void testLambda() throws Exception { testUri.run("ESTwoKeyNav", "$filter=CollPropertyComp/all( l : true )") .goFilter().is(">>"); + testUri.run("ESTwoKeyNav", "$filter=CollPropertyComp/all( x : x/PropertyInt16 eq 2)") + .goFilter().is(" eq <2>>>>"); + testUri.run("ESTwoKeyNav", "$filter=CollPropertyComp/any( l : true )") .goFilter().is(">>"); testUri.run("ESTwoKeyNav", "$filter=CollPropertyComp/any( )") diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/validator/UriValidatorTest.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/validator/UriValidatorTest.java index 1835befd7..db3930e99 100644 --- a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/validator/UriValidatorTest.java +++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/validator/UriValidatorTest.java @@ -6,9 +6,9 @@ * 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 @@ -18,6 +18,9 @@ */ package org.apache.olingo.server.core.uri.validator; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + import org.apache.olingo.commons.api.edm.Edm; import org.apache.olingo.commons.api.http.HttpMethod; import org.apache.olingo.commons.core.edm.provider.EdmProviderImpl; @@ -31,9 +34,6 @@ import org.apache.olingo.server.tecsvc.provider.ContainerProvider; import org.apache.olingo.server.tecsvc.provider.EdmTechProvider; import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; - public class UriValidatorTest { private static final String URI_ALL = "$all"; @@ -77,7 +77,7 @@ public class UriValidatorTest { private static final String QO_SKIPTOKEN = "$skiptoken=123"; private static final String QO_TOP = "$top=1"; - private String[][] urisWithValidSystemQueryOptions = { + private final String[][] urisWithValidSystemQueryOptions = { { URI_ALL, QO_FILTER }, { URI_ALL, QO_FORMAT }, { URI_ALL, QO_EXPAND }, { URI_ALL, QO_COUNT }, { URI_ALL, QO_ORDERBY }, /* { URI_ALL, QO_SEARCH }, */{ URI_ALL, QO_SELECT }, { URI_ALL, QO_SKIP }, { URI_ALL, QO_SKIPTOKEN }, { URI_ALL, QO_TOP }, @@ -105,7 +105,7 @@ public class UriValidatorTest { { URI_REFERENCES, QO_FILTER }, { URI_REFERENCES, QO_FORMAT }, { URI_REFERENCES, QO_ORDERBY }, /* { URI_REFERENCES, QO_SEARCH }, */{ URI_REFERENCES, QO_SKIP }, { URI_REFERENCES, QO_SKIPTOKEN }, - { URI_REFERENCES, QO_TOP }, + { URI_REFERENCES, QO_TOP }, { URI_REFERENCES, QO_ID }, { URI_REFERENCE, QO_FORMAT }, @@ -160,7 +160,7 @@ public class UriValidatorTest { { ContainerProvider.AIRT_STRING } }; - private String[][] urisWithNonValidSystemQueryOptions = { + private final String[][] urisWithNonValidSystemQueryOptions = { { URI_ALL, QO_ID }, { URI_BATCH, QO_FILTER }, { URI_BATCH, QO_FORMAT }, { URI_BATCH, QO_ID }, { URI_BATCH, QO_EXPAND }, @@ -199,7 +199,7 @@ public class UriValidatorTest { /* { URI_MEDIA_STREAM, QO_SEARCH }, */ { URI_MEDIA_STREAM, QO_SELECT }, { URI_MEDIA_STREAM, QO_SKIP }, { URI_MEDIA_STREAM, QO_SKIPTOKEN }, { URI_MEDIA_STREAM, QO_TOP }, - { URI_REFERENCES, QO_ID }, { URI_REFERENCES, QO_EXPAND }, { URI_REFERENCES, QO_COUNT }, + { URI_REFERENCES, QO_EXPAND }, { URI_REFERENCES, QO_COUNT }, { URI_REFERENCES, QO_SELECT }, { URI_REFERENCE, QO_FILTER }, { URI_REFERENCE, QO_ID }, { URI_REFERENCE, QO_EXPAND }, diff --git a/samples/server/src/main/java/org/apache/olingo/server/sample/data/DataProvider.java b/samples/server/src/main/java/org/apache/olingo/server/sample/data/DataProvider.java index d59d25154..2e44e3596 100644 --- a/samples/server/src/main/java/org/apache/olingo/server/sample/data/DataProvider.java +++ b/samples/server/src/main/java/org/apache/olingo/server/sample/data/DataProvider.java @@ -6,9 +6,9 @@ * 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 @@ -37,10 +37,11 @@ import org.apache.olingo.commons.core.data.EntityImpl; import org.apache.olingo.commons.core.data.EntitySetImpl; import org.apache.olingo.commons.core.data.PropertyImpl; import org.apache.olingo.server.api.uri.UriParameter; +import org.apache.olingo.server.sample.edmprovider.CarsEdmProvider; public class DataProvider { - private Map data; + private final Map data; public DataProvider() { data = new HashMap(); @@ -133,6 +134,9 @@ public class DataProvider { .addProperty(createPrimitive("Price", 167189.00)) .addProperty(createPrimitive("Currency", "EUR"))); + for (Entity entity:entitySet.getEntities()) { + entity.setType(CarsEdmProvider.ET_CAR.getFullQualifiedNameAsString()); + } return entitySet; } @@ -149,6 +153,9 @@ public class DataProvider { .addProperty(createPrimitive("Name", "Horse Powered Racing")) .addProperty(createAddress("Horse Street 1", "Maranello", "41053", "Italy"))); + for (Entity entity:entitySet.getEntities()) { + entity.setType(CarsEdmProvider.ET_MANUFACTURER.getFullQualifiedNameAsString()); + } return entitySet; } diff --git a/samples/server/src/main/java/org/apache/olingo/server/sample/processor/CarsProcessor.java b/samples/server/src/main/java/org/apache/olingo/server/sample/processor/CarsProcessor.java index 891acbbb6..71b827daf 100644 --- a/samples/server/src/main/java/org/apache/olingo/server/sample/processor/CarsProcessor.java +++ b/samples/server/src/main/java/org/apache/olingo/server/sample/processor/CarsProcessor.java @@ -6,9 +6,9 @@ * 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 @@ -74,7 +74,8 @@ public class CarsProcessor implements EntityCollectionProcessor, EntityProcessor PrimitiveProcessor, PrimitiveValueProcessor, ComplexProcessor { private OData odata; - private DataProvider dataProvider; + private final DataProvider dataProvider; + private ServiceMetadata edm; // This constructor is application specific and not mandatory for the Olingo library. We use it here to simulate the // database access @@ -85,6 +86,7 @@ public class CarsProcessor implements EntityCollectionProcessor, EntityProcessor @Override public void init(OData odata, ServiceMetadata edm) { this.odata = odata; + this.edm = edm; } @Override @@ -105,7 +107,7 @@ public class CarsProcessor implements EntityCollectionProcessor, EntityProcessor // Now the content is serialized using the serializer. final ExpandOption expand = uriInfo.getExpandOption(); final SelectOption select = uriInfo.getSelectOption(); - InputStream serializedContent = serializer.entityCollection(edmEntitySet.getEntityType(), entitySet, + InputStream serializedContent = serializer.entityCollection(edm, edmEntitySet.getEntityType(), entitySet, EntityCollectionSerializerOptions.with() .contextURL(format == ODataFormat.JSON_NO_METADATA ? null : getContextUrl(edmEntitySet, false, expand, select, null)) @@ -143,7 +145,7 @@ public class CarsProcessor implements EntityCollectionProcessor, EntityProcessor ODataSerializer serializer = odata.createSerializer(format); final ExpandOption expand = uriInfo.getExpandOption(); final SelectOption select = uriInfo.getSelectOption(); - InputStream serializedContent = serializer.entity(edmEntitySet.getEntityType(), entity, + InputStream serializedContent = serializer.entity(edm, edmEntitySet.getEntityType(), entity, EntitySerializerOptions.with() .contextURL(format == ODataFormat.JSON_NO_METADATA ? null : getContextUrl(edmEntitySet, true, expand, select, null)) @@ -256,7 +258,7 @@ public class CarsProcessor implements EntityCollectionProcessor, EntityProcessor final ContextURL contextURL = format == ODataFormat.JSON_NO_METADATA ? null : getContextUrl(edmEntitySet, true, null, null, edmProperty.getName()); InputStream serializerContent = complex ? - serializer.complex((EdmComplexType) edmProperty.getType(), property, + serializer.complex(edm, (EdmComplexType) edmProperty.getType(), property, ComplexSerializerOptions.with().contextURL(contextURL).build()) : serializer.primitive((EdmPrimitiveType) edmProperty.getType(), property, PrimitiveSerializerOptions.with() @@ -273,7 +275,7 @@ public class CarsProcessor implements EntityCollectionProcessor, EntityProcessor } } } - + private Entity readEntityInternal(final UriInfoResource uriInfo, final EdmEntitySet entitySet) throws DataProvider.DataProviderException { // This method will extract the key values and pass them to the data provider