Java tutorial
/** * 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 java.util.regex.Pattern; 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.camel.component.olingo2.api.impl.SystemQueryOption; 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.http.entity.ContentType; 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.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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; /** * 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 FQN_MANUFACTURERS = "DefaultContainer.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 = FQN_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 final String TEST_SERVICE_URL = "http://localhost:8080/MyFormula.svc"; // private static final String TEST_SERVICE_URL = "http://localhost:8080/cars-annotations-sample/MyFormula.svc"; // private static final ContentType TEST_FORMAT = ContentType.APPLICATION_XML_CS_UTF_8; private static final ContentType TEST_FORMAT = ContentType.APPLICATION_JSON; private static final String INDEX = "/index.jsp"; private static final Pattern LINK_PATTERN = Pattern.compile("[^(]+\\('([^']+)'\\)"); private static final String ID_PROPERTY = "Id"; 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()); // wait for generated data to be registered in server Thread.sleep(2000); } @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 testReadDeleteCreateLinks() 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); //Deleting relationships through links is not supported in Olingo2 at the time of writing this test /* final TestOlingo2ResponseHandler<HttpStatusCodes> statusHandler = new TestOlingo2ResponseHandler<HttpStatusCodes>(); final ArrayList<Map<String, Object>> carKeys = new ArrayList<Map<String, Object>>(); for (String carLink : links) { final Matcher matcher = LINK_PATTERN.matcher(carLink); assertTrue("Link pattern " + carLink, matcher.matches()); final String carId = matcher.group(1); final HashMap<String, Object> keys = new HashMap<String, Object>(); keys.put(ID_PROPERTY, carId); carKeys.add(keys); // delete manufacturer->car link statusHandler.reset(); final String resourcePath = TEST_MANUFACTURER_LINKS_CARS + "('" + carId + "')"; olingoApp.delete(resourcePath, statusHandler); assertEquals("Delete car link " + resourcePath, HttpStatusCodes.OK.getStatusCode(), statusHandler.await().getStatusCode()); } // add links to all Cars statusHandler.reset(); olingoApp.create(edm, TEST_MANUFACTURER_LINKS_CARS, carKeys, statusHandler); assertEquals("Links update", HttpStatusCodes.ACCEPTED.getStatusCode(), statusHandler.await().getStatusCode()); // delete car->manufacturer link statusHandler.reset(); olingoApp.delete(TEST_CAR_LINK_MANUFACTURER, statusHandler); assertEquals("Delete manufacturer link " + TEST_CAR_LINK_MANUFACTURER, HttpStatusCodes.OK.getStatusCode(), statusHandler.await().getStatusCode()); // add link to Manufacturer statusHandler.reset(); final HashMap<String, Object> manufacturerKey = new HashMap<String, Object>(); manufacturerKey.put(ID_PROPERTY, "1"); olingoApp.create(edm, TEST_CAR_LINK_MANUFACTURER, manufacturerKey, statusHandler); assertEquals("Link update", HttpStatusCodes.ACCEPTED.getStatusCode(), statusHandler.await().getStatusCode()); */ } @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_PROPERTY, "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, ContentType.APPLICATION_FORM_URLENCODED, 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; } } }