http://git-wip-us.apache.org/repos/asf/camel/blob/9a92064c/components/camel-olingo2/camel-olingo2-api/src/test/java/org/apache/camel/component/olingo2/api/Olingo2AppIntegrationTest.java ---------------------------------------------------------------------- diff --git a/components/camel-olingo2/camel-olingo2-api/src/test/java/org/apache/camel/component/olingo2/api/Olingo2AppIntegrationTest.java b/components/camel-olingo2/camel-olingo2-api/src/test/java/org/apache/camel/component/olingo2/api/Olingo2AppIntegrationTest.java new file mode 100644 index 0000000..d1cfb2a --- /dev/null +++ b/components/camel-olingo2/camel-olingo2-api/src/test/java/org/apache/camel/component/olingo2/api/Olingo2AppIntegrationTest.java @@ -0,0 +1,560 @@ +/** + * 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.camel.component.olingo2.api; + +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import org.apache.camel.component.olingo2.api.batch.Olingo2BatchChangeRequest; +import org.apache.camel.component.olingo2.api.batch.Olingo2BatchQueryRequest; +import org.apache.camel.component.olingo2.api.batch.Olingo2BatchRequest; +import org.apache.camel.component.olingo2.api.batch.Olingo2BatchResponse; +import org.apache.camel.component.olingo2.api.batch.Operation; +import org.apache.camel.component.olingo2.api.impl.AbstractFutureCallback; +import org.apache.camel.component.olingo2.api.impl.Olingo2AppImpl; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.concurrent.FutureCallback; +import org.apache.http.entity.ByteArrayEntity; +import org.apache.olingo.odata2.api.commons.HttpStatusCodes; +import org.apache.olingo.odata2.api.edm.Edm; +import org.apache.olingo.odata2.api.edm.EdmEntitySetInfo; +import org.apache.olingo.odata2.api.ep.entry.ODataEntry; +import org.apache.olingo.odata2.api.ep.feed.ODataDeltaFeed; +import org.apache.olingo.odata2.api.ep.feed.ODataFeed; +import org.apache.olingo.odata2.api.exception.ODataApplicationException; +import org.apache.olingo.odata2.api.servicedocument.Collection; +import org.apache.olingo.odata2.api.servicedocument.ServiceDocument; +import org.apache.olingo.odata2.core.commons.ContentType; +import org.apache.olingo.odata2.core.uri.SystemQueryOption; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Integration test for {@link org.apache.camel.component.olingo2.api.impl.Olingo2AppImpl}. + * To test run the sample Olingo2 Server as outlined at + * http://olingo.apache.org/doc/tutorials/Olingo2V2BasicClientSample.html + */ +public class Olingo2AppIntegrationTest { + + private static final Logger LOG = LoggerFactory.getLogger(Olingo2AppIntegrationTest.class); + private static final long TIMEOUT = 10; + + private static final String MANUFACTURERS = "Manufacturers"; + private static final String ADDRESS = "Address"; + private static final String CARS = "Cars"; + + private static final String TEST_KEY = "'1'"; + private static final String TEST_CREATE_KEY = "'123'"; + private static final String TEST_MANUFACTURER = MANUFACTURERS + "(" + TEST_KEY + ")"; + private static final String TEST_CREATE_MANUFACTURER = MANUFACTURERS + "(" + TEST_CREATE_KEY + ")"; + + private static final String TEST_RESOURCE_CONTENT_ID = "1"; + private static final String TEST_RESOURCE = "$" + TEST_RESOURCE_CONTENT_ID; + + private static final char NEW_LINE = '\n'; + private static final String TEST_CAR = "Manufacturers('1')/Cars('1')"; + private static final String TEST_MANUFACTURER_FOUNDED_PROPERTY = "Manufacturers('1')/Founded"; + private static final String TEST_MANUFACTURER_FOUNDED_VALUE = "Manufacturers('1')/Founded/$value"; + private static final String FOUNDED_PROPERTY = "Founded"; + private static final String TEST_MANUFACTURER_ADDRESS_PROPERTY = "Manufacturers('1')/Address"; + private static final String TEST_MANUFACTURER_LINKS_CARS = "Manufacturers('1')/$links/Cars"; + private static final String TEST_CAR_LINK_MANUFACTURER = "Cars('1')/$links/Manufacturer"; + private static final String COUNT_OPTION = "/$count"; + + private static String TEST_SERVICE_URL = "http://localhost:8080/MyFormula.svc"; + // private static String TEST_SERVICE_URL = "http://localhost:8080/cars-annotations-sample/MyFormula.svc"; +// private static ContentType TEST_FORMAT = ContentType.APPLICATION_XML_CS_UTF_8; + private static ContentType TEST_FORMAT = ContentType.APPLICATION_JSON_CS_UTF_8; + private static final String INDEX = "/index.jsp"; + + private static Olingo2App olingoApp; + private static final String GEN_SAMPLE_DATA = "genSampleData=true"; + private static Edm edm; + + @BeforeClass + public static void beforeClass() throws Exception { + + olingoApp = new Olingo2AppImpl(TEST_SERVICE_URL); + olingoApp.setContentType(TEST_FORMAT.toString()); + + LOG.info("Generate sample data "); + generateSampleData(TEST_SERVICE_URL); + + LOG.info("Read Edm "); + final TestOlingo2ResponseHandler<Edm> responseHandler = new TestOlingo2ResponseHandler<Edm>(); + + olingoApp.read(null, Olingo2AppImpl.METADATA, null, responseHandler); + + edm = responseHandler.await(); + LOG.info("Read default EntityContainer: {}", responseHandler.await().getDefaultEntityContainer().getName()); + } + + @AfterClass + public static void afterClass() { + olingoApp.close(); + } + + @Test + public void testServiceDocument() throws Exception { + final TestOlingo2ResponseHandler<ServiceDocument> responseHandler = + new TestOlingo2ResponseHandler<ServiceDocument>(); + + olingoApp.read(null, "", null, responseHandler); + + final ServiceDocument serviceDocument = responseHandler.await(); + final List<Collection> collections = serviceDocument.getAtomInfo().getWorkspaces().get(0).getCollections(); + assertEquals("Service Atom Collections", 3, collections.size()); + LOG.info("Service Atom Collections: {}", collections); + + final List<EdmEntitySetInfo> entitySetsInfo = serviceDocument.getEntitySetsInfo(); + assertEquals("Service Entity Sets", 3, entitySetsInfo.size()); + LOG.info("Service Document Entries: {}", entitySetsInfo); + } + + @Test + public void testReadFeed() throws Exception { + final TestOlingo2ResponseHandler<ODataFeed> responseHandler = new TestOlingo2ResponseHandler<ODataFeed>(); + + olingoApp.read(edm, MANUFACTURERS, null, responseHandler); + + final ODataFeed dataFeed = responseHandler.await(); + assertNotNull("Data feed", dataFeed); + LOG.info("Entries: {}", prettyPrint(dataFeed)); + } + + @Test + public void testReadEntry() throws Exception { + final TestOlingo2ResponseHandler<ODataEntry> responseHandler = new TestOlingo2ResponseHandler<ODataEntry>(); + + olingoApp.read(edm, TEST_MANUFACTURER, null, responseHandler); + ODataEntry entry = responseHandler.await(); + LOG.info("Single Entry: {}", prettyPrint(entry)); + + responseHandler.reset(); + + olingoApp.read(edm, TEST_CAR, null, responseHandler); + entry = responseHandler.await(); + LOG.info("Single Entry: {}", prettyPrint(entry)); + + responseHandler.reset(); + final Map<String, String> queryParams = new HashMap<String, String>(); + queryParams.put(SystemQueryOption.$expand.toString(), CARS); + + olingoApp.read(edm, TEST_MANUFACTURER, queryParams, responseHandler); + + ODataEntry entryExpanded = responseHandler.await(); + LOG.info("Single Entry with expanded Cars relation: {}", prettyPrint(entryExpanded)); + } + + @Test + public void testReadUpdateProperties() throws Exception { + // test simple property Manufacturer.Founded + final TestOlingo2ResponseHandler<Map<String, Object>> propertyHandler = + new TestOlingo2ResponseHandler<Map<String, Object>>(); + + olingoApp.read(edm, TEST_MANUFACTURER_FOUNDED_PROPERTY, null, propertyHandler); + + Calendar founded = (Calendar) propertyHandler.await().get(FOUNDED_PROPERTY); + LOG.info("Founded property {}", founded.toString()); + + final TestOlingo2ResponseHandler<Calendar> valueHandler = new TestOlingo2ResponseHandler<Calendar>(); + + olingoApp.read(edm, TEST_MANUFACTURER_FOUNDED_VALUE, null, valueHandler); + + founded = valueHandler.await(); + LOG.info("Founded property {}", founded.toString()); + + final TestOlingo2ResponseHandler<HttpStatusCodes> statusHandler = + new TestOlingo2ResponseHandler<HttpStatusCodes>(); + final HashMap<String, Object> properties = new HashMap<String, Object>(); + properties.put(FOUNDED_PROPERTY, new Date()); + +// olingoApp.update(edm, TEST_MANUFACTURER_FOUNDED_PROPERTY, properties, statusHandler); + // requires a plain Date for XML + olingoApp.update(edm, TEST_MANUFACTURER_FOUNDED_PROPERTY, new Date(), statusHandler); + + LOG.info("Founded property updated with status {}", statusHandler.await().getStatusCode()); + + statusHandler.reset(); + + olingoApp.update(edm, TEST_MANUFACTURER_FOUNDED_VALUE, new Date(), statusHandler); + + LOG.info("Founded property updated with status {}", statusHandler.await().getStatusCode()); + + // test complex property Manufacturer.Address + propertyHandler.reset(); + + olingoApp.read(edm, TEST_MANUFACTURER_ADDRESS_PROPERTY, null, propertyHandler); + + final Map<String, Object> address = propertyHandler.await(); + LOG.info("Address property {}", prettyPrint(address, 0)); + + statusHandler.reset(); + + address.clear(); + // Olingo2 sample server MERGE/PATCH behaves like PUT!!! +// address.put("Street", "Main Street"); + address.put("Street", "Star Street 137"); + address.put("City", "Stuttgart"); + address.put("ZipCode", "70173"); + address.put("Country", "Germany"); + +// olingoApp.patch(edm, TEST_MANUFACTURER_ADDRESS_PROPERTY, address, statusHandler); + olingoApp.merge(edm, TEST_MANUFACTURER_ADDRESS_PROPERTY, address, statusHandler); + + LOG.info("Address property updated with status {}", statusHandler.await().getStatusCode()); + } + + @Test + public void testReadLinks() throws Exception { + final TestOlingo2ResponseHandler<List<String>> linksHandler = new TestOlingo2ResponseHandler<List<String>>(); + + olingoApp.read(edm, TEST_MANUFACTURER_LINKS_CARS, null, linksHandler); + + final List<String> links = linksHandler.await(); + assertFalse(links.isEmpty()); + LOG.info("Read links: {}", links); + + final TestOlingo2ResponseHandler<String> linkHandler = new TestOlingo2ResponseHandler<String>(); + + olingoApp.read(edm, TEST_CAR_LINK_MANUFACTURER, null, linkHandler); + + final String link = linkHandler.await(); + LOG.info("Read link: {}", link); + } + + @Test + public void testReadCount() throws Exception { + final TestOlingo2ResponseHandler<Long> countHandler = new TestOlingo2ResponseHandler<Long>(); + + olingoApp.read(edm, MANUFACTURERS + COUNT_OPTION, null, countHandler); + + LOG.info("Manufacturers count: {}", countHandler.await()); + + countHandler.reset(); + olingoApp.read(edm, TEST_MANUFACTURER + COUNT_OPTION, null, countHandler); + + LOG.info("Manufacturer count: {}", countHandler.await()); + + countHandler.reset(); + olingoApp.read(edm, TEST_MANUFACTURER_LINKS_CARS + COUNT_OPTION, null, countHandler); + + LOG.info("Manufacturers links count: {}", countHandler.await()); + + countHandler.reset(); + olingoApp.read(edm, TEST_CAR_LINK_MANUFACTURER + COUNT_OPTION, null, countHandler); + + LOG.info("Manufacturer link count: {}", countHandler.await()); + } + + @Test + public void testCreateUpdateDeleteEntry() throws Exception { + + // create entry to update + final TestOlingo2ResponseHandler<ODataEntry> entryHandler = new TestOlingo2ResponseHandler<ODataEntry>(); + + olingoApp.create(edm, MANUFACTURERS, getEntityData(), entryHandler); + + ODataEntry createdEntry = entryHandler.await(); + LOG.info("Created Entry: {}", prettyPrint(createdEntry)); + + Map<String, Object> data = getEntityData(); + @SuppressWarnings("unchecked") + Map<String, Object> address = (Map<String, Object>) data.get(ADDRESS); + + data.put("Name", "MyCarManufacturer Renamed"); + address.put("Street", "Main Street"); + final TestOlingo2ResponseHandler<HttpStatusCodes> statusHandler = + new TestOlingo2ResponseHandler<HttpStatusCodes>(); + + olingoApp.update(edm, TEST_CREATE_MANUFACTURER, data, statusHandler); + statusHandler.await(); + + statusHandler.reset(); + data.put("Name", "MyCarManufacturer Patched"); + olingoApp.patch(edm, TEST_CREATE_MANUFACTURER, data, statusHandler); + statusHandler.await(); + + entryHandler.reset(); + olingoApp.read(edm, TEST_CREATE_MANUFACTURER, null, entryHandler); + + ODataEntry updatedEntry = entryHandler.await(); + LOG.info("Updated Entry successfully: {}", prettyPrint(updatedEntry)); + + statusHandler.reset(); + olingoApp.delete(TEST_CREATE_MANUFACTURER, statusHandler); + + HttpStatusCodes statusCode = statusHandler.await(); + LOG.info("Deletion of Entry was successful: {}: {}", statusCode.getStatusCode(), statusCode.getInfo()); + + try { + LOG.info("Verify Delete Entry"); + + entryHandler.reset(); + olingoApp.read(edm, TEST_CREATE_MANUFACTURER, null, entryHandler); + + entryHandler.await(); + fail("Entry not deleted!"); + } catch (Exception e) { + LOG.info("Deleted entry not found: {}", e.getMessage()); + } + } + + @Test + public void testBatchRequest() throws Exception { + + final List<Olingo2BatchRequest> batchParts = new ArrayList<Olingo2BatchRequest>(); + + // Edm query + batchParts.add(Olingo2BatchQueryRequest.resourcePath(Olingo2AppImpl.METADATA).build()); + + // feed query + batchParts.add(Olingo2BatchQueryRequest.resourcePath(MANUFACTURERS).build()); + + // read + batchParts.add(Olingo2BatchQueryRequest.resourcePath(TEST_MANUFACTURER).build()); + + // read with expand + final HashMap<String, String> queryParams = new HashMap<String, String>(); + queryParams.put(SystemQueryOption.$expand.toString(), CARS); + batchParts.add(Olingo2BatchQueryRequest.resourcePath(TEST_MANUFACTURER).queryParams(queryParams).build()); + + // create + final Map<String, Object> data = getEntityData(); + batchParts.add(Olingo2BatchChangeRequest.resourcePath(MANUFACTURERS). + contentId(TEST_RESOURCE_CONTENT_ID).operation(Operation.CREATE).body(data).build()); + + // update + final Map<String, Object> updateData = new HashMap<String, Object>(data); + @SuppressWarnings("unchecked") + Map<String, Object> address = (Map<String, Object>) updateData.get(ADDRESS); + updateData.put("Name", "MyCarManufacturer Renamed"); + address.put("Street", "Main Street"); + + batchParts.add(Olingo2BatchChangeRequest.resourcePath(TEST_RESOURCE).operation(Operation.UPDATE) + .body(updateData).build()); + + // delete + batchParts.add(Olingo2BatchChangeRequest.resourcePath(TEST_RESOURCE).operation(Operation.DELETE).build()); + + final TestOlingo2ResponseHandler<List<Olingo2BatchResponse>> responseHandler = + new TestOlingo2ResponseHandler<List<Olingo2BatchResponse>>(); + + // read to verify delete + batchParts.add(Olingo2BatchQueryRequest.resourcePath(TEST_CREATE_MANUFACTURER).build()); + + olingoApp.batch(edm, batchParts, responseHandler); + + final List<Olingo2BatchResponse> responseParts = responseHandler.await(15, TimeUnit.MINUTES); + assertEquals("Batch responses expected", 8, responseParts.size()); + + assertNotNull(responseParts.get(0).getBody()); + final ODataFeed feed = (ODataFeed) responseParts.get(1).getBody(); + assertNotNull(feed); + LOG.info("Batch feed: {}", prettyPrint(feed)); + + ODataEntry dataEntry = (ODataEntry) responseParts.get(2).getBody(); + assertNotNull(dataEntry); + LOG.info("Batch read entry: {}", prettyPrint(dataEntry)); + + dataEntry = (ODataEntry) responseParts.get(3).getBody(); + assertNotNull(dataEntry); + LOG.info("Batch read entry with expand: {}", prettyPrint(dataEntry)); + + dataEntry = (ODataEntry) responseParts.get(4).getBody(); + assertNotNull(dataEntry); + LOG.info("Batch create entry: {}", prettyPrint(dataEntry)); + + assertEquals(HttpStatusCodes.NO_CONTENT.getStatusCode(), responseParts.get(5).getStatusCode()); + assertEquals(HttpStatusCodes.NO_CONTENT.getStatusCode(), responseParts.get(6).getStatusCode()); + + assertEquals(HttpStatusCodes.NOT_FOUND.getStatusCode(), responseParts.get(7).getStatusCode()); + final Exception exception = (Exception) responseParts.get(7).getBody(); + assertNotNull(exception); + LOG.info("Batch retrieve deleted entry: {}", exception); + } + + private Map<String, Object> getEntityData() { + Map<String, Object> data = new HashMap<String, Object>(); + data.put("Id", "123"); + data.put("Name", "MyCarManufacturer"); + data.put(FOUNDED_PROPERTY, new Date()); + Map<String, Object> address = new HashMap<String, Object>(); + address.put("Street", "Main"); + address.put("ZipCode", "42421"); + address.put("City", "Fairy City"); + address.put("Country", "FarFarAway"); + data.put(ADDRESS, address); + return data; + } + + private static String prettyPrint(ODataFeed dataFeed) { + StringBuilder builder = new StringBuilder(); + builder.append("[\n"); + for (ODataEntry entry : dataFeed.getEntries()) { + builder.append(prettyPrint(entry.getProperties(), 1)).append('\n'); + } + builder.append("]\n"); + return builder.toString(); + } + + private static String prettyPrint(ODataEntry createdEntry) { + return prettyPrint(createdEntry.getProperties(), 0); + } + + private static String prettyPrint(Map<String, Object> properties, int level) { + StringBuilder b = new StringBuilder(); + Set<Map.Entry<String, Object>> entries = properties.entrySet(); + + for (Map.Entry<String, Object> entry : entries) { + indent(b, level); + b.append(entry.getKey()).append(": "); + Object value = entry.getValue(); + if (value instanceof Map) { + @SuppressWarnings("unchecked") + final Map<String, Object> objectMap = (Map<String, Object>) value; + value = prettyPrint(objectMap, level + 1); + b.append(value).append(NEW_LINE); + } else if (value instanceof Calendar) { + Calendar cal = (Calendar) value; + value = SimpleDateFormat.getInstance().format(cal.getTime()); + b.append(value).append(NEW_LINE); + } else if (value instanceof ODataDeltaFeed) { + ODataDeltaFeed feed = (ODataDeltaFeed) value; + List<ODataEntry> inlineEntries = feed.getEntries(); + b.append("{"); + for (ODataEntry oDataEntry : inlineEntries) { + value = prettyPrint(oDataEntry.getProperties(), level + 1); + b.append("\n[\n").append(value).append("\n],"); + } + b.deleteCharAt(b.length() - 1); + indent(b, level); + b.append("}\n"); + } else { + b.append(value).append(NEW_LINE); + } + } + // remove last line break + b.deleteCharAt(b.length() - 1); + return b.toString(); + } + + private static void indent(StringBuilder builder, int indentLevel) { + for (int i = 0; i < indentLevel; i++) { + builder.append(" "); + } + } + + private static void generateSampleData(String serviceUrl) throws IOException { + final HttpPost httpUriRequest = new HttpPost(serviceUrl.substring(0, serviceUrl.lastIndexOf('/')) + INDEX); + httpUriRequest.setEntity(new ByteArrayEntity(GEN_SAMPLE_DATA.getBytes())); + ((Olingo2AppImpl)olingoApp).execute(httpUriRequest, Olingo2AppImpl.APPLICATION_FORM_URL_ENCODED, + new FutureCallback<HttpResponse>() { + @Override + public void completed(HttpResponse result) { + try { + AbstractFutureCallback.checkStatus(result); + LOG.info("Sample data generated {}", result.getStatusLine()); + } catch (ODataApplicationException e) { + LOG.error("Sample data generation error: " + e.getMessage(), e); + } + } + + @Override + public void failed(Exception ex) { + LOG.error("Error generating sample data " + ex.getMessage(), ex); + } + + @Override + public void cancelled() { + LOG.error("Sample data generation canceled!"); + } + }); + } + + private static final class TestOlingo2ResponseHandler<T> implements Olingo2ResponseHandler<T> { + + private T response; + private Exception error; + private CountDownLatch latch = new CountDownLatch(1); + + @Override + public void onResponse(T response) { + this.response = response; + if (LOG.isDebugEnabled()) { + if (response instanceof ODataFeed) { + LOG.debug("Received response: {}", prettyPrint((ODataFeed) response)); + } else if (response instanceof ODataEntry) { + LOG.debug("Received response: {}", prettyPrint((ODataEntry) response)); + } else { + LOG.debug("Received response: {}", response); + } + } + latch.countDown(); + } + + @Override + public void onException(Exception ex) { + error = ex; + latch.countDown(); + } + + @Override + public void onCanceled() { + error = new IllegalStateException("Request Canceled"); + latch.countDown(); + } + + public T await() throws Exception { + return await(TIMEOUT, TimeUnit.SECONDS); + } + + public T await(long timeout, TimeUnit unit) throws Exception { + assertTrue("Timeout waiting for response", latch.await(timeout, unit)); + if (error != null) { + throw error; + } + assertNotNull("Response", response); + return response; + } + + public void reset() { + latch.countDown(); + latch = new CountDownLatch(1); + response = null; + error = null; + } + } +} \ No newline at end of file
http://git-wip-us.apache.org/repos/asf/camel/blob/9a92064c/components/camel-olingo2/camel-olingo2-api/src/test/resources/log4j.properties ---------------------------------------------------------------------- diff --git a/components/camel-olingo2/camel-olingo2-api/src/test/resources/log4j.properties b/components/camel-olingo2/camel-olingo2-api/src/test/resources/log4j.properties new file mode 100644 index 0000000..af87137 --- /dev/null +++ b/components/camel-olingo2/camel-olingo2-api/src/test/resources/log4j.properties @@ -0,0 +1,39 @@ +# +# 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. +# + +# +# The logging properties used for testing. +# +log4j.rootLogger=INFO, file + +# uncomment the following line to turn on API debug messages +#log4j.logger.org.apache.camel.component.olingo2.api=DEBUG + +# uncomment the following line to turn on HTTP Client debug messages +#log4j.logger.httpclient.wire=DEBUG + +# CONSOLE appender not used by default +log4j.appender.out=org.apache.log4j.ConsoleAppender +log4j.appender.out.layout=org.apache.log4j.PatternLayout +#log4j.appender.out.layout.ConversionPattern=[%30.30t] %-30.30c{1} %-5p %m%n +log4j.appender.out.layout.ConversionPattern=%d [%-15.15t] %-5p %-30.30c{1} - %m%n + +# File appender +log4j.appender.file=org.apache.log4j.FileAppender +log4j.appender.file.layout=org.apache.log4j.PatternLayout +log4j.appender.file.layout.ConversionPattern=%d [%-15.15t] %-5p %-30.30c{1} - %m%n +log4j.appender.file.file=target/camel-olingo2-test.log http://git-wip-us.apache.org/repos/asf/camel/blob/9a92064c/components/camel-olingo2/camel-olingo2-component/pom.xml ---------------------------------------------------------------------- diff --git a/components/camel-olingo2/camel-olingo2-component/pom.xml b/components/camel-olingo2/camel-olingo2-component/pom.xml new file mode 100644 index 0000000..fc9942c --- /dev/null +++ b/components/camel-olingo2/camel-olingo2-component/pom.xml @@ -0,0 +1,208 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.camel.component.olingo2</groupId> + <artifactId>camel-olingo2-parent</artifactId> + <version>2.14-SNAPSHOT</version> + </parent> + + <artifactId>camel-olingo2</artifactId> + <packaging>bundle</packaging> + <name>Camel :: Olingo2 Component</name> + <description>Camel Component for Apache Olingo2</description> + + <properties> + <schemeName>olingo2</schemeName> + <componentName>Olingo2</componentName> + <componentPackage>org.apache.camel.component.olingo2</componentPackage> + <outPackage>org.apache.camel.component.olingo2.internal</outPackage> + + <camel.osgi.export.pkg>${componentPackage}</camel.osgi.export.pkg> + <camel.osgi.private.pkg>${outPackage}</camel.osgi.private.pkg> + </properties> + + <dependencies> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-core</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.apache.camel.component.olingo2</groupId> + <artifactId>camel-olingo2-api</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>commons-lang</groupId> + <artifactId>commons-lang</artifactId> + <version>${commons-lang-version}</version> + </dependency> + + <!-- Camel annotations in provided scope to avoid compile errors in IDEs --> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>spi-annotations</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + + <!-- Component API javadoc in provided scope to read API signatures --> + <dependency> + <groupId>org.apache.camel.component.olingo2</groupId> + <artifactId>camel-olingo2-api</artifactId> + <version>${project.version}</version> + <classifier>javadoc</classifier> + <scope>provided</scope> + </dependency> + + <!-- logging --> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + <version>1.7.7</version> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-log4j12</artifactId> + <version>1.7.7</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>log4j</groupId> + <artifactId>log4j</artifactId> + <version>1.2.17</version> + <scope>test</scope> + </dependency> + + <!-- testing --> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-test</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <defaultGoal>install</defaultGoal> + + <plugins> + + <!-- generate Component source and test source --> + <plugin> + <groupId>org.apache.camel</groupId> + <artifactId>camel-api-component-maven-plugin</artifactId> + <executions> + <execution> + <id>generate-test-component-classes</id> + <goals> + <goal>fromApis</goal> + </goals> + <configuration> + <apis> + <api> + <apiName/> + <proxyClass>org.apache.camel.component.olingo2.api.Olingo2App</proxyClass> + <fromSignatureFile>src/signatures/olingo-api-signature.txt</fromSignatureFile> + <excludeConfigNames>edm|responseHandler</excludeConfigNames> + <extraOptions> + <extraOption> + <name>keyPredicate</name> + <type>java.lang.String</type> + </extraOption> + </extraOptions> + <nullableOptions> + <nullableOption>queryParams</nullableOption> + </nullableOptions> + </api> + </apis> + </configuration> + </execution> + </executions> + </plugin> + + <!-- add generated source and test source to build --> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>build-helper-maven-plugin</artifactId> + <executions> + <execution> + <id>add-generated-sources</id> + <goals> + <goal>add-source</goal> + </goals> + <configuration> + <sources> + <source>${project.build.directory}/generated-sources/camel-component</source> + </sources> + </configuration> + </execution> + <execution> + <id>add-generated-test-sources</id> + <goals> + <goal>add-test-source</goal> + </goals> + <configuration> + <sources> + <source>${project.build.directory}/generated-test-sources/camel-component</source> + </sources> + </configuration> + </execution> + </executions> + </plugin> + + </plugins> + + <pluginManagement> + <plugins> + <plugin> + <groupId>org.apache.camel</groupId> + <artifactId>camel-api-component-maven-plugin</artifactId> + <version>${project.version}</version> + <configuration> + <scheme>${schemeName}</scheme> + <componentName>${componentName}</componentName> + <componentPackage>${componentPackage}</componentPackage> + <outPackage>${outPackage}</outPackage> + </configuration> + </plugin> + </plugins> + </pluginManagement> + + </build> + + <reporting> + <plugins> + <plugin> + <groupId>org.apache.camel</groupId> + <artifactId>camel-api-component-maven-plugin</artifactId> + <version>${project.version}</version> + <configuration> + <scheme>${schemeName}</scheme> + <componentName>${componentName}</componentName> + <componentPackage>${componentPackage}</componentPackage> + <outPackage>${outPackage}</outPackage> + </configuration> + </plugin> + </plugins> + </reporting> + +</project> http://git-wip-us.apache.org/repos/asf/camel/blob/9a92064c/components/camel-olingo2/camel-olingo2-component/src/main/java/org/apache/camel/component/olingo2/Olingo2Component.java ---------------------------------------------------------------------- diff --git a/components/camel-olingo2/camel-olingo2-component/src/main/java/org/apache/camel/component/olingo2/Olingo2Component.java b/components/camel-olingo2/camel-olingo2-component/src/main/java/org/apache/camel/component/olingo2/Olingo2Component.java new file mode 100644 index 0000000..771a3ec --- /dev/null +++ b/components/camel-olingo2/camel-olingo2-component/src/main/java/org/apache/camel/component/olingo2/Olingo2Component.java @@ -0,0 +1,152 @@ +/** + * 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.camel.component.olingo2; + +import java.util.Map; + +import javax.net.ssl.SSLContext; + +import org.apache.camel.CamelContext; +import org.apache.camel.Endpoint; +import org.apache.camel.component.olingo2.api.Olingo2App; +import org.apache.camel.component.olingo2.api.impl.Olingo2AppImpl; +import org.apache.camel.component.olingo2.internal.Olingo2ApiCollection; +import org.apache.camel.component.olingo2.internal.Olingo2ApiName; +import org.apache.camel.spi.UriEndpoint; +import org.apache.camel.util.component.AbstractApiComponent; +import org.apache.http.HttpHost; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.impl.nio.client.HttpAsyncClientBuilder; + +/** + * Represents the component that manages {@link Olingo2Endpoint}. + */ +@UriEndpoint(scheme = "olingo2", consumerClass = Olingo2Consumer.class, consumerPrefix = "consumer") +public class Olingo2Component extends AbstractApiComponent<Olingo2ApiName, Olingo2Configuration, Olingo2ApiCollection> { + + // component level shared proxy + private Olingo2App apiProxy; + + public Olingo2Component() { + super(Olingo2Endpoint.class, Olingo2ApiName.class, Olingo2ApiCollection.getCollection()); + } + + public Olingo2Component(CamelContext context) { + super(context, Olingo2Endpoint.class, Olingo2ApiName.class, Olingo2ApiCollection.getCollection()); + } + + @Override + protected Olingo2ApiName getApiName(String apiNameStr) throws IllegalArgumentException { + return Olingo2ApiName.fromValue(apiNameStr); + } + + @Override + protected Endpoint createEndpoint(String uri, String remaining, Map<String, Object> parameters) throws Exception { + // parse remaining to extract resourcePath and queryParams + final String[] pathSegments = remaining.split("/", -1); + final String methodName = pathSegments[0]; + + if (pathSegments.length > 1) { + final StringBuilder resourcePath = new StringBuilder(); + for (int i = 1; i < pathSegments.length; i++) { + resourcePath.append(pathSegments[i]); + if (i < (pathSegments.length - 1)) { + resourcePath.append('/'); + } + } + // This will override any URI supplied ?resourcePath=... param + parameters.put(Olingo2Endpoint.RESOURCE_PATH_PROPERTY, resourcePath.toString()); + } + + final Olingo2Configuration endpointConfiguration = createEndpointConfiguration(Olingo2ApiName.DEFAULT); + final Endpoint endpoint = createEndpoint(uri, methodName, Olingo2ApiName.DEFAULT, endpointConfiguration); + + // set endpoint property inBody + setProperties(endpoint, parameters); + + // configure endpoint properties and initialize state + endpoint.configureProperties(parameters); + + return endpoint; + } + + @Override + protected Endpoint createEndpoint(String uri, String methodName, Olingo2ApiName apiName, + Olingo2Configuration endpointConfiguration) { + return new Olingo2Endpoint(uri, this, apiName, methodName, endpointConfiguration); + } + + public Olingo2App createApiProxy(Olingo2Configuration endpointConfiguration) { + final Olingo2App result; + if (endpointConfiguration.equals(this.configuration)) { + synchronized (this) { + if (apiProxy == null) { + apiProxy = createOlingo2App(this.configuration); + } + } + result = apiProxy; + } else { + result = createOlingo2App(endpointConfiguration); + } + return result; + } + + private Olingo2App createOlingo2App(Olingo2Configuration configuration) { + + HttpAsyncClientBuilder clientBuilder = configuration.getHttpAsyncClientBuilder(); + if (clientBuilder == null) { + clientBuilder = HttpAsyncClientBuilder.create(); + + // apply simple configuration properties + final RequestConfig.Builder requestConfigBuilder = RequestConfig.custom(); + requestConfigBuilder.setConnectTimeout(configuration.getConnectTimeout()); + requestConfigBuilder.setSocketTimeout(configuration.getSocketTimeout()); + + final HttpHost proxy = configuration.getProxy(); + if (proxy != null) { + requestConfigBuilder.setProxy(proxy); + } + + // set default request config + clientBuilder.setDefaultRequestConfig(requestConfigBuilder.build()); + + final SSLContext context = configuration.getSslContext(); + if (context != null) { + clientBuilder.setSSLContext(context); + } + } + + apiProxy = new Olingo2AppImpl(configuration.getServiceUri(), clientBuilder); + apiProxy.setContentType(configuration.getContentType()); + + return apiProxy; + } + + public void closeApiProxy(Olingo2App apiProxy) { + if (this.apiProxy != apiProxy) { + // not a shared proxy + apiProxy.close(); + } + } + + @Override + protected void doStop() throws Exception { + if (apiProxy != null) { + apiProxy.close(); + } + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/9a92064c/components/camel-olingo2/camel-olingo2-component/src/main/java/org/apache/camel/component/olingo2/Olingo2Configuration.java ---------------------------------------------------------------------- diff --git a/components/camel-olingo2/camel-olingo2-component/src/main/java/org/apache/camel/component/olingo2/Olingo2Configuration.java b/components/camel-olingo2/camel-olingo2-component/src/main/java/org/apache/camel/component/olingo2/Olingo2Configuration.java new file mode 100644 index 0000000..e6ec348 --- /dev/null +++ b/components/camel-olingo2/camel-olingo2-component/src/main/java/org/apache/camel/component/olingo2/Olingo2Configuration.java @@ -0,0 +1,158 @@ +/** + * 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.camel.component.olingo2; + +import java.util.Map; +import javax.net.ssl.SSLContext; + +import org.apache.camel.spi.UriParam; +import org.apache.camel.spi.UriParams; +import org.apache.commons.lang.builder.HashCodeBuilder; +import org.apache.http.HttpHost; +import org.apache.http.impl.nio.client.HttpAsyncClientBuilder; +import org.apache.olingo.odata2.core.commons.ContentType; + +/** + * Component configuration for Olingo2 component. + */ +@UriParams +public class Olingo2Configuration { + + private static final String DEFAULT_CONTENT_TYPE = ContentType.APPLICATION_JSON_CS_UTF_8.toString(); + private static final int DEFAULT_TIMEOUT = 30 * 1000; + + @UriParam + private String serviceUri; + + @UriParam + private String contentType = DEFAULT_CONTENT_TYPE; + + @UriParam + private Map<String, String> httpHeaders; + + // common connection parameters for convenience + @UriParam + private int connectTimeout = DEFAULT_TIMEOUT; + + @UriParam + private int socketTimeout = DEFAULT_TIMEOUT; + + @UriParam + private HttpHost proxy; + + @UriParam + private SSLContext sslContext; + + // for more complex configuration, use a client builder + @UriParam + private HttpAsyncClientBuilder httpAsyncClientBuilder; + + public String getServiceUri() { + return serviceUri; + } + + public void setServiceUri(String serviceUri) { + this.serviceUri = serviceUri; + } + + public String getContentType() { + return contentType; + } + + public void setContentType(String contentType) { + this.contentType = contentType; + } + + public Map<String, String> getHttpHeaders() { + return httpHeaders; + } + + public void setHttpHeaders(Map<String, String> httpHeaders) { + this.httpHeaders = httpHeaders; + } + + public int getConnectTimeout() { + return connectTimeout; + } + + public void setConnectTimeout(int connectTimeout) { + this.connectTimeout = connectTimeout; + } + + public int getSocketTimeout() { + return socketTimeout; + } + + public void setSocketTimeout(int socketTimeout) { + this.socketTimeout = socketTimeout; + } + + public HttpHost getProxy() { + return proxy; + } + + public void setProxy(HttpHost proxy) { + this.proxy = proxy; + } + + public SSLContext getSslContext() { + return sslContext; + } + + public void setSslContext(SSLContext sslContext) { + this.sslContext = sslContext; + } + + public HttpAsyncClientBuilder getHttpAsyncClientBuilder() { + return httpAsyncClientBuilder; + } + + public void setHttpAsyncClientBuilder(HttpAsyncClientBuilder httpAsyncClientBuilder) { + this.httpAsyncClientBuilder = httpAsyncClientBuilder; + } + + @Override + public int hashCode() { + return new HashCodeBuilder() + .append(serviceUri) + .append(contentType) + .append(httpHeaders) + .append(connectTimeout) + .append(socketTimeout) + .append(proxy) + .append(sslContext) + .append(httpAsyncClientBuilder) + .hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof Olingo2Configuration) { + Olingo2Configuration other = (Olingo2Configuration) obj; + return serviceUri == null ? other.serviceUri == null : serviceUri.equals(other.serviceUri) + && contentType == null ? other.contentType == null : contentType.equals(other.contentType) + && httpHeaders == null ? other.httpHeaders == null : httpHeaders.equals(other.httpHeaders) + && connectTimeout == other.connectTimeout + && socketTimeout == other.socketTimeout + && proxy == null ? other.proxy == null : proxy.equals(other.proxy) + && sslContext == null ? other.sslContext == null : sslContext.equals(other.sslContext) + && httpAsyncClientBuilder == null ? other.httpAsyncClientBuilder == null + : httpAsyncClientBuilder.equals(other.httpAsyncClientBuilder); + } + return false; + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/9a92064c/components/camel-olingo2/camel-olingo2-component/src/main/java/org/apache/camel/component/olingo2/Olingo2Consumer.java ---------------------------------------------------------------------- diff --git a/components/camel-olingo2/camel-olingo2-component/src/main/java/org/apache/camel/component/olingo2/Olingo2Consumer.java b/components/camel-olingo2/camel-olingo2-component/src/main/java/org/apache/camel/component/olingo2/Olingo2Consumer.java new file mode 100644 index 0000000..72206d1 --- /dev/null +++ b/components/camel-olingo2/camel-olingo2-component/src/main/java/org/apache/camel/component/olingo2/Olingo2Consumer.java @@ -0,0 +1,93 @@ +/** + * 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.camel.component.olingo2; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CountDownLatch; + +import org.apache.camel.Processor; +import org.apache.camel.RuntimeCamelException; +import org.apache.camel.component.olingo2.api.Olingo2ResponseHandler; +import org.apache.camel.util.ObjectHelper; +import org.apache.camel.util.component.AbstractApiConsumer; + +import org.apache.camel.component.olingo2.internal.Olingo2ApiName; +import org.apache.camel.util.component.ApiConsumerHelper; + +/** + * The Olingo2 consumer. + */ +public class Olingo2Consumer extends AbstractApiConsumer<Olingo2ApiName, Olingo2Configuration> { + + public Olingo2Consumer(Olingo2Endpoint endpoint, Processor processor) { + super(endpoint, processor); + } + + @Override + protected int poll() throws Exception { + // invoke the consumer method + final Map<String, Object> args = new HashMap<String, Object>(); + args.putAll(endpoint.getEndpointProperties()); + + // let the endpoint and the Consumer intercept properties + endpoint.interceptProperties(args); + interceptProperties(args); + + try { + // create responseHandler + final CountDownLatch latch = new CountDownLatch(1); + final Object[] result = new Object[1]; + final Exception[] error = new Exception[1]; + + args.put(Olingo2Endpoint.RESPONSE_HANDLER_PROPERTY, new Olingo2ResponseHandler<Object>() { + @Override + public void onResponse(Object response) { + result[0] = response; + latch.countDown(); + } + + @Override + public void onException(Exception ex) { + error[0] = ex; + latch.countDown(); + } + + @Override + public void onCanceled() { + error[0] = new RuntimeCamelException("Http Request cancelled"); + latch.countDown(); + } + }); + + doInvokeMethod(args); + + // guaranteed to return, since an exception on timeout is expected!!! + latch.await(); + + if (error[0] != null) { + throw error[0]; + } + + return ApiConsumerHelper.getResultsProcessed(this, result[0], isSplitResult()); + + } catch (Throwable t) { + throw ObjectHelper.wrapRuntimeCamelException(t); + } + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/9a92064c/components/camel-olingo2/camel-olingo2-component/src/main/java/org/apache/camel/component/olingo2/Olingo2Endpoint.java ---------------------------------------------------------------------- diff --git a/components/camel-olingo2/camel-olingo2-component/src/main/java/org/apache/camel/component/olingo2/Olingo2Endpoint.java b/components/camel-olingo2/camel-olingo2-component/src/main/java/org/apache/camel/component/olingo2/Olingo2Endpoint.java new file mode 100644 index 0000000..170eb49 --- /dev/null +++ b/components/camel-olingo2/camel-olingo2-component/src/main/java/org/apache/camel/component/olingo2/Olingo2Endpoint.java @@ -0,0 +1,281 @@ +/** + * 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.camel.component.olingo2; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CountDownLatch; + +import org.apache.camel.Consumer; +import org.apache.camel.Processor; +import org.apache.camel.Producer; +import org.apache.camel.RuntimeCamelException; +import org.apache.camel.component.olingo2.api.Olingo2App; +import org.apache.camel.component.olingo2.api.Olingo2ResponseHandler; +import org.apache.camel.component.olingo2.internal.Olingo2ApiCollection; +import org.apache.camel.component.olingo2.internal.Olingo2ApiName; +import org.apache.camel.component.olingo2.internal.Olingo2Constants; +import org.apache.camel.component.olingo2.internal.Olingo2PropertiesHelper; +import org.apache.camel.spi.UriEndpoint; +import org.apache.camel.util.component.AbstractApiEndpoint; +import org.apache.camel.util.component.ApiMethod; +import org.apache.camel.util.component.ApiMethodPropertiesHelper; +import org.apache.olingo.odata2.api.edm.Edm; + +/** + * Represents a Olingo2 endpoint. + */ +@UriEndpoint(scheme = "olingo2", consumerClass = Olingo2Consumer.class, consumerPrefix = "consumer") +public class Olingo2Endpoint extends AbstractApiEndpoint<Olingo2ApiName, Olingo2Configuration> { + + protected static final String RESOURCE_PATH_PROPERTY = "resourcePath"; + protected static final String RESPONSE_HANDLER_PROPERTY = "responseHandler"; + + private static final String KEY_PREDICATE_PROPERTY = "keyPredicate"; + private static final String QUERY_PARAMS_PROPERTY = "queryParams"; + + private static final String READ_METHOD = "read"; + private static final String EDM_PROPERTY = "edm"; + private static final String DATA_PROPERTY = "data"; + private static final String DELETE_METHOD = "delete"; + + private final Set<String> endpointPropertyNames; + + private Olingo2App apiProxy; + + private volatile Edm edm; + + public Olingo2Endpoint(String uri, Olingo2Component component, + Olingo2ApiName apiName, String methodName, Olingo2Configuration endpointConfiguration) { + super(uri, component, apiName, methodName, Olingo2ApiCollection.getCollection().getHelper(apiName), endpointConfiguration); + + // get all endpoint property names + endpointPropertyNames = new HashSet<String>(getPropertiesHelper().getValidEndpointProperties(configuration)); + // avoid adding edm as queryParam + endpointPropertyNames.add(EDM_PROPERTY); + } + + public Producer createProducer() throws Exception { + return new Olingo2Producer(this); + } + + public Consumer createConsumer(Processor processor) throws Exception { + // make sure inBody is not set for consumers + if (inBody != null) { + throw new IllegalArgumentException("Option inBody is not supported for consumer endpoint"); + } + // only read method is supported + if (!READ_METHOD.equals(methodName)) { + throw new IllegalArgumentException("Only read method is supported for consumer endpoints"); + } + final Olingo2Consumer consumer = new Olingo2Consumer(this, processor); + // also set consumer.* properties + configureConsumer(consumer); + return consumer; + } + + @Override + protected ApiMethodPropertiesHelper<Olingo2Configuration> getPropertiesHelper() { + return Olingo2PropertiesHelper.getHelper(); + } + + protected String getThreadProfileName() { + return Olingo2Constants.THREAD_PROFILE_NAME; + } + + @Override + public void configureProperties(Map<String, Object> options) { + // handle individual query params + parseQueryParams(options); + + super.configureProperties(options); + } + + @Override + protected void afterConfigureProperties() { + // set default inBody + if (!(READ_METHOD.equals(methodName) || DELETE_METHOD.equals(methodName)) + && inBody == null) { + inBody = DATA_PROPERTY; + } + createProxy(); + } + + @Override + public synchronized Object getApiProxy(ApiMethod method, Map<String, Object> args) { + return apiProxy; + } + + @Override + public Olingo2Component getComponent() { + return (Olingo2Component) super.getComponent(); + } + + @Override + protected void doStart() throws Exception { + if (apiProxy == null) { + createProxy(); + } + } + + @Override + protected void doStop() throws Exception { + if (apiProxy != null) { + // close the apiProxy + getComponent().closeApiProxy(apiProxy); + apiProxy = null; + } + } + + @Override + public void interceptPropertyNames(Set<String> propertyNames) { + // add edm, and responseHandler property names + // edm is computed on first call to getApiProxy(), and responseHandler is provided by consumer and producer + if (!DELETE_METHOD.equals(methodName)) { + propertyNames.add(EDM_PROPERTY); + } + propertyNames.add(RESPONSE_HANDLER_PROPERTY); + } + + @Override + public void interceptProperties(Map<String, Object> properties) { + + // read Edm if not set yet + properties.put(EDM_PROPERTY, readEdm()); + + // handle keyPredicate + final String keyPredicate = (String) properties.get(KEY_PREDICATE_PROPERTY); + if (keyPredicate != null) { + + // make sure a resource path is provided + final String resourcePath = (String) properties.get(RESOURCE_PATH_PROPERTY); + if (resourcePath == null) { + throw new IllegalArgumentException("Resource path must be provided in endpoint URI, or URI parameter '" + + RESOURCE_PATH_PROPERTY + "', or exchange header '" + + Olingo2Constants.PROPERTY_PREFIX + RESOURCE_PATH_PROPERTY + "'"); + } + + // append keyPredicate to dynamically create resource path + properties.put(RESOURCE_PATH_PROPERTY, resourcePath + '(' + keyPredicate + ')'); + } + + // handle individual queryParams + parseQueryParams(properties); + } + + private void createProxy() { + apiProxy = getComponent().createApiProxy(getConfiguration()); + } + + private void parseQueryParams(Map<String, Object> options) { + // extract non-endpoint properties as query params + final Map<String, String> queryParams = new HashMap<String, String>(); + for (Iterator<Map.Entry<String, Object>> it = options.entrySet().iterator(); it.hasNext();) { + + final Map.Entry<String, Object> entry = it.next(); + final String paramName = entry.getKey(); + + if (!endpointPropertyNames.contains(paramName)) { + + // add to query params + final Object value = entry.getValue(); + if (value == null) { + throw new IllegalArgumentException("Null value for query parameter " + paramName); + } + queryParams.put(paramName, value.toString()); + + // remove entry from supplied options + it.remove(); + } + } + if (!queryParams.isEmpty()) { + + @SuppressWarnings("unchecked") + final Map<String, String> oldParams = (Map<String, String>) options.get(QUERY_PARAMS_PROPERTY); + if (oldParams == null) { + // set queryParams property + options.put(QUERY_PARAMS_PROPERTY, queryParams); + } else { + // overwrite old params in supplied map + oldParams.putAll(queryParams); + } + + } + } + + /// double checked locking based singleton Edm reader + private Edm readEdm() { + + Edm localEdm = edm; + if (localEdm == null) { + + synchronized (this) { + + localEdm = edm; + if (localEdm == null) { + + final CountDownLatch latch = new CountDownLatch(1); + final Exception[] error = new Exception[1]; + apiProxy.read(null, "$metadata", null, new Olingo2ResponseHandler<Edm>() { + + @Override + public void onResponse(Edm response) { + edm = response; + latch.countDown(); + } + + @Override + public void onException(Exception ex) { + error[0] = ex; + latch.countDown(); + } + + @Override + public void onCanceled() { + error[0] = new RuntimeCamelException("OData HTTP request cancelled"); + latch.countDown(); + } + }); + + try { + // wait until response or timeout + latch.await(); + + final Exception ex = error[0]; + if (ex != null) { + if (ex instanceof RuntimeCamelException) { + throw (RuntimeCamelException) ex; + } else { + throw new RuntimeCamelException("Error reading EDM " + ex.getMessage(), ex); + } + } + + } catch (InterruptedException e) { + throw new RuntimeCamelException(e.getMessage(), e); + } + + localEdm = edm; + } + } + } + + return localEdm; + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/9a92064c/components/camel-olingo2/camel-olingo2-component/src/main/java/org/apache/camel/component/olingo2/Olingo2Producer.java ---------------------------------------------------------------------- diff --git a/components/camel-olingo2/camel-olingo2-component/src/main/java/org/apache/camel/component/olingo2/Olingo2Producer.java b/components/camel-olingo2/camel-olingo2-component/src/main/java/org/apache/camel/component/olingo2/Olingo2Producer.java new file mode 100644 index 0000000..5286af6 --- /dev/null +++ b/components/camel-olingo2/camel-olingo2-component/src/main/java/org/apache/camel/component/olingo2/Olingo2Producer.java @@ -0,0 +1,106 @@ +/** + * 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.camel.component.olingo2; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.camel.AsyncCallback; +import org.apache.camel.Exchange; +import org.apache.camel.RuntimeCamelException; +import org.apache.camel.component.olingo2.api.Olingo2ResponseHandler; +import org.apache.camel.component.olingo2.internal.Olingo2ApiName; +import org.apache.camel.component.olingo2.internal.Olingo2PropertiesHelper; +import org.apache.camel.util.ObjectHelper; +import org.apache.camel.util.component.AbstractApiProducer; +import org.apache.camel.util.component.ApiMethod; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The Olingo2 producer. + */ +public class Olingo2Producer extends AbstractApiProducer<Olingo2ApiName, Olingo2Configuration> { + + private static final Logger LOG = LoggerFactory.getLogger(Olingo2Producer.class); + + public Olingo2Producer(Olingo2Endpoint endpoint) { + super(endpoint, Olingo2PropertiesHelper.getHelper()); + } + + @Override + public boolean process(final Exchange exchange, final AsyncCallback callback) { + // properties for method arguments + final Map<String, Object> properties = new HashMap<String, Object>(); + properties.putAll(endpoint.getEndpointProperties()); + propertiesHelper.getExchangeProperties(exchange, properties); + + // let the endpoint and the Producer intercept properties + endpoint.interceptProperties(properties); + interceptProperties(properties); + + // create response handler + properties.put(Olingo2Endpoint.RESPONSE_HANDLER_PROPERTY, new Olingo2ResponseHandler<Object>() { + @Override + public void onResponse(Object response) { + // producer returns a single response, even for methods with List return types + exchange.getOut().setBody(response); + // copy headers + exchange.getOut().setHeaders(exchange.getIn().getHeaders()); + + interceptResult(response, exchange); + + callback.done(false); + } + + @Override + public void onException(Exception ex) { + exchange.setException(ex); + callback.done(false); + } + + @Override + public void onCanceled() { + exchange.setException(new RuntimeCamelException("HTTP Request cancelled for " + + endpoint.getEndpointUri())); + callback.done(false); + } + }); + + // decide which method to invoke + final ApiMethod method = findMethod(exchange, properties); + if (method == null) { + // synchronous failure + callback.done(true); + return true; + } + + if (LOG.isDebugEnabled()) { + LOG.debug("Invoking operation {} with {}", method.getName(), properties.keySet()); + } + + try { + doInvokeMethod(method, properties); + } catch (Throwable t) { + exchange.setException(ObjectHelper.wrapRuntimeCamelException(t)); + callback.done(true); + return true; + } + + return false; + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/9a92064c/components/camel-olingo2/camel-olingo2-component/src/main/java/org/apache/camel/component/olingo2/internal/Olingo2Constants.java ---------------------------------------------------------------------- diff --git a/components/camel-olingo2/camel-olingo2-component/src/main/java/org/apache/camel/component/olingo2/internal/Olingo2Constants.java b/components/camel-olingo2/camel-olingo2-component/src/main/java/org/apache/camel/component/olingo2/internal/Olingo2Constants.java new file mode 100644 index 0000000..96644ba --- /dev/null +++ b/components/camel-olingo2/camel-olingo2-component/src/main/java/org/apache/camel/component/olingo2/internal/Olingo2Constants.java @@ -0,0 +1,29 @@ +/** + * 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.camel.component.olingo2.internal; + +/** + * Constants for Olingo2 component. + */ +public interface Olingo2Constants { + + // prefix for parameters when passed as exchange header properties + String PROPERTY_PREFIX = "CamelOlingo2."; + + // thread profile name for this component + String THREAD_PROFILE_NAME = "CamelOlingo2"; +} http://git-wip-us.apache.org/repos/asf/camel/blob/9a92064c/components/camel-olingo2/camel-olingo2-component/src/main/java/org/apache/camel/component/olingo2/internal/Olingo2PropertiesHelper.java ---------------------------------------------------------------------- diff --git a/components/camel-olingo2/camel-olingo2-component/src/main/java/org/apache/camel/component/olingo2/internal/Olingo2PropertiesHelper.java b/components/camel-olingo2/camel-olingo2-component/src/main/java/org/apache/camel/component/olingo2/internal/Olingo2PropertiesHelper.java new file mode 100644 index 0000000..a8110de --- /dev/null +++ b/components/camel-olingo2/camel-olingo2-component/src/main/java/org/apache/camel/component/olingo2/internal/Olingo2PropertiesHelper.java @@ -0,0 +1,39 @@ +/** + * 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.camel.component.olingo2.internal; + +import org.apache.camel.component.olingo2.Olingo2Configuration; +import org.apache.camel.util.component.ApiMethodPropertiesHelper; + +/** + * Singleton {@link ApiMethodPropertiesHelper} for Olingo2 component. + */ +public final class Olingo2PropertiesHelper extends ApiMethodPropertiesHelper<Olingo2Configuration> { + + private static Olingo2PropertiesHelper helper; + + private Olingo2PropertiesHelper() { + super(Olingo2Configuration.class, Olingo2Constants.PROPERTY_PREFIX); + } + + public static synchronized Olingo2PropertiesHelper getHelper() { + if (helper == null) { + helper = new Olingo2PropertiesHelper(); + } + return helper; + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/9a92064c/components/camel-olingo2/camel-olingo2-component/src/main/resources/META-INF/services/org/apache/camel/component/olingo2 ---------------------------------------------------------------------- diff --git a/components/camel-olingo2/camel-olingo2-component/src/main/resources/META-INF/services/org/apache/camel/component/olingo2 b/components/camel-olingo2/camel-olingo2-component/src/main/resources/META-INF/services/org/apache/camel/component/olingo2 new file mode 100644 index 0000000..a715916 --- /dev/null +++ b/components/camel-olingo2/camel-olingo2-component/src/main/resources/META-INF/services/org/apache/camel/component/olingo2 @@ -0,0 +1 @@ +class=org.apache.camel.component.olingo2.Olingo2Component http://git-wip-us.apache.org/repos/asf/camel/blob/9a92064c/components/camel-olingo2/camel-olingo2-component/src/signatures/olingo-api-signature.txt ---------------------------------------------------------------------- diff --git a/components/camel-olingo2/camel-olingo2-component/src/signatures/olingo-api-signature.txt b/components/camel-olingo2/camel-olingo2-component/src/signatures/olingo-api-signature.txt new file mode 100644 index 0000000..f4bdace --- /dev/null +++ b/components/camel-olingo2/camel-olingo2-component/src/signatures/olingo-api-signature.txt @@ -0,0 +1,7 @@ +void read(org.apache.olingo.odata2.api.edm.Edm edm, String resourcePath, java.util.Map<String, String> queryParams, org.apache.camel.component.olingo2.api.Olingo2ResponseHandler responseHandler); +void delete(String resourcePath, org.apache.camel.component.olingo2.api.Olingo2ResponseHandler responseHandler); +void create(org.apache.olingo.odata2.api.edm.Edm edm, String resourcePath, Object data, org.apache.camel.component.olingo2.api.Olingo2ResponseHandler responseHandler); +void update(org.apache.olingo.odata2.api.edm.Edm edm, String resourcePath, Object data, org.apache.camel.component.olingo2.api.Olingo2ResponseHandler responseHandler); +void patch(org.apache.olingo.odata2.api.edm.Edm edm, String resourcePath, Object data, org.apache.camel.component.olingo2.api.Olingo2ResponseHandler responseHandler); +void merge(org.apache.olingo.odata2.api.edm.Edm edm, String resourcePath, Object data, org.apache.camel.component.olingo2.api.Olingo2ResponseHandler responseHandler); +void batch(org.apache.olingo.odata2.api.edm.Edm edm, Object data, org.apache.camel.component.olingo2.api.Olingo2ResponseHandler responseHandler); http://git-wip-us.apache.org/repos/asf/camel/blob/9a92064c/components/camel-olingo2/camel-olingo2-component/src/test/java/org/apache/camel/component/olingo2/AbstractOlingo2TestSupport.java ---------------------------------------------------------------------- diff --git a/components/camel-olingo2/camel-olingo2-component/src/test/java/org/apache/camel/component/olingo2/AbstractOlingo2TestSupport.java b/components/camel-olingo2/camel-olingo2-component/src/test/java/org/apache/camel/component/olingo2/AbstractOlingo2TestSupport.java new file mode 100644 index 0000000..094f0b1 --- /dev/null +++ b/components/camel-olingo2/camel-olingo2-component/src/test/java/org/apache/camel/component/olingo2/AbstractOlingo2TestSupport.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.camel.component.olingo2; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import org.apache.camel.CamelContext; +import org.apache.camel.CamelExecutionException; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.apache.camel.util.IntrospectionSupport; + +/** + * Abstract base class for Olingo Integration tests generated by Camel API component maven plugin. + */ +public class AbstractOlingo2TestSupport extends CamelTestSupport { + + private static final String TEST_OPTIONS_PROPERTIES = "/test-options.properties"; + + @Override + protected CamelContext createCamelContext() throws Exception { + + final CamelContext context = super.createCamelContext(); + + // read Olingo component configuration from TEST_OPTIONS_PROPERTIES + final Properties properties = new Properties(); + try { + properties.load(getClass().getResourceAsStream(TEST_OPTIONS_PROPERTIES)); + } catch (Exception e) { + throw new IOException(String.format("%s could not be loaded: %s", TEST_OPTIONS_PROPERTIES, e.getMessage()), + e); + } + + Map<String, Object> options = new HashMap<String, Object>(); + for (Map.Entry<Object, Object> entry : properties.entrySet()) { + options.put(entry.getKey().toString(), entry.getValue()); + } + + final Olingo2Configuration configuration = new Olingo2Configuration(); + IntrospectionSupport.setProperties(configuration, options); + + // add OlingoComponent to Camel context + final Olingo2Component component = new Olingo2Component(context); + component.setConfiguration(configuration); + context.addComponent("olingo2", component); + + return context; + } + + @Override + public boolean isCreateCamelContextPerClass() { + // only create the context once for this class + return true; + } + + @SuppressWarnings("unchecked") + protected <T> T requestBodyAndHeaders(String endpointUri, Object body, Map<String, Object> headers) + throws CamelExecutionException { + return (T) template().requestBodyAndHeaders(endpointUri, body, headers); + } + + @SuppressWarnings("unchecked") + protected <T> T requestBody(String endpoint, Object body) throws CamelExecutionException { + return (T) template().requestBody(endpoint, body); + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/9a92064c/components/camel-olingo2/camel-olingo2-component/src/test/java/org/apache/camel/component/olingo2/Olingo2AppIntegrationTest.java ---------------------------------------------------------------------- diff --git a/components/camel-olingo2/camel-olingo2-component/src/test/java/org/apache/camel/component/olingo2/Olingo2AppIntegrationTest.java b/components/camel-olingo2/camel-olingo2-component/src/test/java/org/apache/camel/component/olingo2/Olingo2AppIntegrationTest.java new file mode 100644 index 0000000..87ff736 --- /dev/null +++ b/components/camel-olingo2/camel-olingo2-component/src/test/java/org/apache/camel/component/olingo2/Olingo2AppIntegrationTest.java @@ -0,0 +1,235 @@ +/** + * 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.camel.component.olingo2; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.olingo2.api.batch.Olingo2BatchChangeRequest; +import org.apache.camel.component.olingo2.api.batch.Olingo2BatchQueryRequest; +import org.apache.camel.component.olingo2.api.batch.Olingo2BatchRequest; +import org.apache.camel.component.olingo2.api.batch.Olingo2BatchResponse; +import org.apache.camel.component.olingo2.api.batch.Operation; +import org.apache.camel.component.olingo2.api.impl.Olingo2AppImpl; +import org.apache.camel.component.olingo2.internal.Olingo2Constants; +import org.apache.olingo.odata2.api.commons.HttpStatusCodes; +import org.apache.olingo.odata2.api.edm.Edm; +import org.apache.olingo.odata2.api.ep.entry.ODataEntry; +import org.apache.olingo.odata2.api.ep.feed.ODataFeed; +import org.apache.olingo.odata2.api.servicedocument.ServiceDocument; +import org.apache.olingo.odata2.core.uri.SystemQueryOption; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Test class for {@link org.apache.camel.component.olingo2.api.Olingo2App} APIs. + * The integration test runs against Apache Olingo 2.0 sample server + * described at http://olingo.apache.org/doc/sample-setup.html + */ +public class Olingo2AppIntegrationTest extends AbstractOlingo2TestSupport { + + private static final Logger LOG = LoggerFactory.getLogger(Olingo2AppIntegrationTest.class); + private static final String ID_PROPERTY = "Id"; + private static final String MANUFACTURERS = "Manufacturers"; + private static final String TEST_MANUFACTURER = "Manufacturers('1')"; + private static final String CARS = "Cars"; + private static final String TEST_RESOURCE_CONTENT_ID = "1"; + private static final String ADDRESS = "Address"; + private static final String TEST_RESOURCE = "$1"; + private static final String TEST_CREATE_MANUFACTURER = "Manufacturers('123')"; + + @Test + public void testRead() throws Exception { + final Map<String, Object> headers = new HashMap<String, Object>(); + + // read ServiceDocument + final ServiceDocument document = requestBodyAndHeaders("direct://READSERVICEDOC", null, headers); + assertNotNull(document); + assertFalse("ServiceDocument entity sets", document.getEntitySetsInfo().isEmpty()); + + // parameter type is java.util.Map + final HashMap<String, String> queryParams = new HashMap<String, String>(); + queryParams.put(SystemQueryOption.$top.name(), "5"); + headers.put("CamelOlingo2.queryParams", queryParams); + + // read ODataFeed + final ODataFeed manufacturers = requestBodyAndHeaders("direct://READFEED", null, headers); + assertNotNull(manufacturers); + assertEquals("Manufacturers feed size", 5, manufacturers.getEntries().size()); + + // read ODataEntry + headers.clear(); + headers.put(Olingo2Constants.PROPERTY_PREFIX + "keyPredicate", "'1'"); + final ODataEntry manufacturer = requestBodyAndHeaders("direct://READENTRY", null, headers); + assertNotNull(manufacturer); + assertEquals("Manufacturer Id", "1", manufacturer.getProperties().get(ID_PROPERTY)); + } + + @Test + public void testCreateUpdateDelete() throws Exception { + final Map<String, Object> data = getEntityData(); + Map<String, Object> address; + + final ODataEntry manufacturer = requestBody("direct://CREATE", data); + assertNotNull("Created Manufacturer", manufacturer); + assertEquals("Created Manufacturer Id", "123", manufacturer.getProperties().get(ID_PROPERTY)); + + // update + data.put("Name", "MyCarManufacturer Renamed"); + address = (Map<String, Object>)data.get("Address"); + address.put("Street", "Main Street"); + + HttpStatusCodes status = requestBody("direct://UPDATE", data); + assertNotNull("Update status", status); + assertEquals("Update status", HttpStatusCodes.NO_CONTENT.getStatusCode(), status.getStatusCode()); + + // delete + status = requestBody("direct://DELETE", null); + assertNotNull("Delete status", status); + assertEquals("Delete status", HttpStatusCodes.NO_CONTENT.getStatusCode(), status.getStatusCode()); + } + + private Map<String, Object> getEntityData() { + final Map<String, Object> data = new HashMap<String, Object>(); + data.put("Id", "123"); + data.put("Name", "MyCarManufacturer"); + data.put("Founded", new Date()); + Map<String, Object> address = new HashMap<String, Object>(); + address.put("Street", "Main"); + address.put("ZipCode", "42421"); + address.put("City", "Fairy City"); + address.put("Country", "FarFarAway"); + data.put("Address", address); + return data; + } + + @Test + public void testBatch() throws Exception { + final List<Olingo2BatchRequest> batchParts = new ArrayList<Olingo2BatchRequest>(); + + // 1. Edm query + batchParts.add(Olingo2BatchQueryRequest.resourcePath(Olingo2AppImpl.METADATA).build()); + + // 2. feed query + batchParts.add(Olingo2BatchQueryRequest.resourcePath(MANUFACTURERS).build()); + + // 3. read + batchParts.add(Olingo2BatchQueryRequest.resourcePath(TEST_MANUFACTURER).build()); + + // 4. read with expand + final HashMap<String, String> queryParams = new HashMap<String, String>(); + queryParams.put(SystemQueryOption.$expand.toString(), CARS); + batchParts.add(Olingo2BatchQueryRequest.resourcePath(TEST_MANUFACTURER).queryParams(queryParams).build()); + + // 5. create + final Map<String, Object> data = getEntityData(); + batchParts.add(Olingo2BatchChangeRequest.resourcePath(MANUFACTURERS). + contentId(TEST_RESOURCE_CONTENT_ID).operation(Operation.CREATE).body(data).build()); + + // 6. update + final Map<String, Object> updateData = new HashMap<String, Object>(data); + @SuppressWarnings("unchecked") + Map<String, Object> address = (Map<String, Object>) updateData.get(ADDRESS); + updateData.put("Name", "MyCarManufacturer Renamed"); + address.put("Street", "Main Street"); + + batchParts.add(Olingo2BatchChangeRequest.resourcePath(TEST_RESOURCE).operation(Operation.UPDATE) + .body(updateData).build()); + + // 7. delete + batchParts.add(Olingo2BatchChangeRequest.resourcePath(TEST_RESOURCE).operation(Operation.DELETE).build()); + + // 8. read to verify delete + batchParts.add(Olingo2BatchQueryRequest.resourcePath(TEST_CREATE_MANUFACTURER).build()); + + // execute batch request + final List<Olingo2BatchResponse> responseParts = requestBody("direct://BATCH", batchParts); + assertNotNull("Batch response", responseParts); + assertEquals("Batch responses expected", 8, responseParts.size()); + + final Edm edm = (Edm) responseParts.get(0).getBody(); + assertNotNull(edm); + + final ODataFeed feed = (ODataFeed) responseParts.get(1).getBody(); + assertNotNull(feed); + + ODataEntry dataEntry = (ODataEntry) responseParts.get(2).getBody(); + assertNotNull(dataEntry); + + dataEntry = (ODataEntry) responseParts.get(3).getBody(); + assertNotNull(dataEntry); + + dataEntry = (ODataEntry) responseParts.get(4).getBody(); + assertNotNull(dataEntry); + + assertEquals(HttpStatusCodes.NO_CONTENT.getStatusCode(), responseParts.get(5).getStatusCode()); + assertEquals(HttpStatusCodes.NO_CONTENT.getStatusCode(), responseParts.get(6).getStatusCode()); + + assertEquals(HttpStatusCodes.NOT_FOUND.getStatusCode(), responseParts.get(7).getStatusCode()); + final Exception exception = (Exception) responseParts.get(7).getBody(); + assertNotNull(exception); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + public void configure() { + // test routes for read + from("direct://READSERVICEDOC") + .to("olingo2://read/"); + + from("direct://READFEED") + .to("olingo2://read/Manufacturers?$orderBy=Name%20asc"); + + from("direct://READENTRY") + .to("olingo2://read/Manufacturers"); + + // test route for create + from("direct://CREATE") + .to("olingo2://create/Manufacturers"); + + // test route for update + from("direct://UPDATE") + .to("olingo2://update/Manufacturers('123')"); + + // test route for delete + from("direct://DELETE") + .to("olingo2://delete/Manufacturers('123')"); + +/* + // test route for merge + from("direct://MERGE") + .to("olingo2://merge"); + + // test route for patch + from("direct://PATCH") + .to("olingo2://patch"); +*/ + + // test route for batch + from("direct://BATCH") + .to("olingo2://batch"); + + } + }; + } +}