package com.temenos.interaction.media.hal; /* * #%L * interaction-media-hal * %% * Copyright (C) 2012 - 2013 Temenos Holdings N.V. * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * #L% */ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.ws.rs.core.GenericEntity; import javax.ws.rs.core.Request; import javax.ws.rs.core.UriInfo; import org.junit.Before; import org.junit.Test; import org.odata4j.core.OCollection; import org.odata4j.core.OCollections; import org.odata4j.core.OComplexObject; import org.odata4j.core.OComplexObjects; import org.odata4j.core.OEntities; import org.odata4j.core.OEntity; import org.odata4j.core.OEntityKey; import org.odata4j.core.OLink; import org.odata4j.core.OProperties; import org.odata4j.core.OProperty; import org.odata4j.edm.EdmCollectionType; import org.odata4j.edm.EdmComplexType; import org.odata4j.edm.EdmEntitySet; import org.odata4j.edm.EdmEntityType; import org.odata4j.edm.EdmProperty; import org.odata4j.edm.EdmSimpleType; import com.fasterxml.jackson.databind.ObjectMapper; import com.temenos.interaction.core.command.CommandHelper; import com.temenos.interaction.core.entity.Entity; import com.temenos.interaction.core.entity.EntityMetadata; import com.temenos.interaction.core.entity.EntityProperties; import com.temenos.interaction.core.entity.Metadata; import com.temenos.interaction.core.entity.vocabulary.Vocabulary; import com.temenos.interaction.core.entity.vocabulary.terms.TermComplexGroup; import com.temenos.interaction.core.entity.vocabulary.terms.TermComplexType; import com.temenos.interaction.core.entity.vocabulary.terms.TermListType; import com.temenos.interaction.core.entity.vocabulary.terms.TermValueType; import com.temenos.interaction.core.hypermedia.Action; import com.temenos.interaction.core.hypermedia.DefaultResourceStateProvider; import com.temenos.interaction.core.hypermedia.ResourceState; import com.temenos.interaction.core.hypermedia.ResourceStateMachine; import com.temenos.interaction.core.hypermedia.ResourceStateProvider; import com.temenos.interaction.core.hypermedia.Transition; import com.temenos.interaction.core.resource.CollectionResource; import com.temenos.interaction.core.resource.EntityResource; import com.temenos.interaction.core.resource.RESTResource; /** Tests for output of complex OEntity structures to HAL JSON */ public class TestNesting { /* shared type objects */ EdmEntitySet setType; EdmEntityType ridersType; EdmCollectionType ridesCollectionType; EdmComplexType ridesType; @Before public void setup() { EdmEntitySet dummy = createMockRidersEntitySet(); } public Map parseJson(String json) throws IOException { //converting json to Map byte[] mapData = json.getBytes(); Map<String,Object> myMap = new HashMap<String, Object>(); ObjectMapper objectMapper = new ObjectMapper(); myMap = objectMapper.readValue(mapData, HashMap.class); return myMap; } private Metadata createMockRiderVocabMetadata() { EntityMetadata vocs = new EntityMetadata("Riders"); Vocabulary vocId = new Vocabulary(); vocId.setTerm(new TermValueType(TermValueType.TEXT)); vocs.setPropertyVocabulary("name", vocId); Vocabulary vocBody = new Vocabulary(); vocBody.setTerm(new TermValueType(TermValueType.INTEGER_NUMBER)); vocs.setPropertyVocabulary("age", vocBody); Vocabulary vocRides = new Vocabulary(); vocRides.setTerm(new TermListType(true)); vocRides.setTerm(new TermComplexType(true)); vocs.setPropertyVocabulary("rides", vocRides); Vocabulary vocHorseName = new Vocabulary(); vocHorseName.setTerm(new TermValueType(TermValueType.TEXT)); vocHorseName.setTerm(new TermComplexGroup("rides")); vocs.setPropertyVocabulary("HorseName", vocHorseName, Collections.enumeration(Collections.singletonList("rides"))); Vocabulary vocHorseSize = new Vocabulary(); vocHorseSize.setTerm(new TermValueType(TermValueType.TEXT)); vocHorseSize.setTerm(new TermComplexGroup("rides")); vocs.setPropertyVocabulary("HorseSize", vocHorseSize, Collections.enumeration(Collections.singletonList("rides"))); Metadata metadata = new Metadata("Family"); metadata.setEntityMetadata(vocs); return metadata; } private String makeSingleLineString(ByteArrayOutputStream bos) throws Exception { String responseString = new String(bos.toByteArray(), "UTF-8"); responseString = responseString.replaceAll(System.getProperty("line.separator"), ""); responseString = responseString.replaceAll(">\\s+<", "><"); return responseString; } /* a rider is a child who rides horses */ private EdmEntitySet createMockRidersEntitySet() { List<EdmProperty.Builder> subprops = new ArrayList<EdmProperty.Builder>(); subprops.add(EdmProperty.newBuilder("HorseName").setType(EdmSimpleType.STRING)); subprops.add(EdmProperty.newBuilder("HorseSize").setType(EdmSimpleType.STRING)); ridesType = EdmComplexType.newBuilder().setNamespace("InteractionTest").setName("RidersRides").addProperties(subprops).build(); List<EdmProperty.Builder> eprops = new ArrayList<EdmProperty.Builder>(); eprops.add(EdmProperty.newBuilder("ID").setType(EdmSimpleType.STRING)); eprops.add(EdmProperty.newBuilder("name").setType(EdmSimpleType.STRING)); eprops.add(EdmProperty.newBuilder("age").setType(EdmSimpleType.STRING)); eprops.add(EdmProperty.newBuilder("rides").setType(new EdmCollectionType(EdmProperty.CollectionKind.Bag, ridesType))); EdmEntityType.Builder eet = EdmEntityType.newBuilder().setNamespace("InteractionTest").setName("Riders").addKeys(Arrays.asList("ID")).addProperties(eprops); EdmEntitySet.Builder ees = EdmEntitySet.newBuilder().setName("Riders").setEntityType(eet); setType = ees.build(); ridersType = (EdmEntityType)setType.getType(); ridesCollectionType = (EdmCollectionType)ridersType.findDeclaredProperty("rides").getType(); ridesType = (EdmComplexType)ridesCollectionType.getItemType(); return setType; } private OComplexObject makeHorse(String name, String size) { List<OProperty<?>> subproperties = new ArrayList<OProperty<?>>(); subproperties.add(OProperties.string("HorseName", name)); subproperties.add(OProperties.string("HorseSize", size)); return OComplexObjects.create(ridesType, subproperties); } /** A structure with a collection of a complex type in it */ @Test public void testSerialiseNestedResource() throws Exception { // the test key OEntityKey entityKey = OEntityKey.create("123"); // the test properties List<OProperty<?>> properties = new ArrayList<OProperty<?>>(); properties.add(OProperties.string("name", "noah")); properties.add(OProperties.string("age", "2")); OCollection rides = OCollections.newBuilder(ridesCollectionType). add(makeHorse("Harley", "12.2")). add(makeHorse("Donny", "13.2")). build(); properties.add(OProperties.collection("Riders_rides", ridesCollectionType, rides)); OEntity entity = OEntities.create(setType, entityKey, properties, new ArrayList<OLink>()); EntityResource<OEntity> er = CommandHelper.createEntityResource(entity, OEntity.class); er.setEntityName("Riders"); HALProvider hp = new HALProvider(createMockRiderVocabMetadata()); UriInfo mockUriInfo = mock(UriInfo.class); when(mockUriInfo.getBaseUri()).thenReturn(new URI("http://www.temenos.com/rest.svc/")); hp.setUriInfo(mockUriInfo); ByteArrayOutputStream bos = new ByteArrayOutputStream(); hp.writeTo(er, EntityResource.class, OEntity.class, null, MediaType.APPLICATION_HAL_JSON_TYPE, null, bos); String expectedJSON = "{'_links':{'self':{'href':'http://www.temenos.com/rest.svc/'}},'age':'2','name':'noah','rides':[{'HorseSize':'12.2','HorseName':'Harley'},{'HorseName':'Donny','HorseSize':'13.2'}]}".replace('\'','\"'); String responseString = makeSingleLineString(bos); System.err.println(responseString); Map<String,Object> expectedData = parseJson(expectedJSON); Map<String,Object> actualData = parseJson(responseString); assertEquals(expectedData, actualData); } /** A structure with a collection of a complex type in it * This collection has only one element; the default behaviour * as of Halbuilder 3 is to remove the js array when there is only * one element. This checks that that behaviour is overridden and * the array is still there. */ @Test public void testSerialiseNestedResource1() throws Exception { // the test key OEntityKey entityKey = OEntityKey.create("123"); // the test properties List<OProperty<?>> properties = new ArrayList<OProperty<?>>(); properties.add(OProperties.string("name", "noah")); properties.add(OProperties.string("age", "2")); OCollection rides = OCollections.newBuilder(ridesCollectionType). add(makeHorse("Harley", "12.2")). // add(makeHorse("Donny", "13.2")). build(); properties.add(OProperties.collection("Riders_rides", ridesCollectionType, rides)); OEntity entity = OEntities.create(setType, entityKey, properties, new ArrayList<OLink>()); EntityResource<OEntity> er = CommandHelper.createEntityResource(entity, OEntity.class); er.setEntityName("Riders"); HALProvider hp = new HALProvider(createMockRiderVocabMetadata()); UriInfo mockUriInfo = mock(UriInfo.class); when(mockUriInfo.getBaseUri()).thenReturn(new URI("http://www.temenos.com/rest.svc/")); hp.setUriInfo(mockUriInfo); ByteArrayOutputStream bos = new ByteArrayOutputStream(); hp.writeTo(er, EntityResource.class, OEntity.class, null, MediaType.APPLICATION_HAL_JSON_TYPE, null, bos); String expectedJSON = "{'_links':{'self':{'href':'http://www.temenos.com/rest.svc/'}},'age':'2','name':'noah','rides':[{'HorseSize':'12.2','HorseName':'Harley'}]}".replace('\'','\"'); String responseString = makeSingleLineString(bos); System.err.println(responseString); Map<String,Object> expectedData = parseJson(expectedJSON); Map<String,Object> actualData = parseJson(responseString); assertEquals(expectedData, actualData); } @Test public void testSerialiseEmptyEntityResource() throws Exception { Metadata vocab = createMockRiderVocabMetadata(); EntityProperties properties = new EmptyEntity().getProperties(vocab.getEntityMetadata("Riders")); Entity emptyEntity = new Entity("", properties); EntityResource<Entity> er = CommandHelper.createEntityResource(emptyEntity, Entity.class); er.setEntityName("Riders"); HALProvider hp = new HALProvider(vocab); UriInfo mockUriInfo = mock(UriInfo.class); when(mockUriInfo.getBaseUri()).thenReturn(new URI("http://www.temenos.com/rest.svc/")); hp.setUriInfo(mockUriInfo); ByteArrayOutputStream bos = new ByteArrayOutputStream(); hp.writeTo(er, EntityResource.class, Entity.class, null, MediaType.APPLICATION_HAL_JSON_TYPE, null, bos); String expectedJSON = "{'_links':{'self':{'href':'http://www.temenos.com/rest.svc/'}},'age':'','name':'','rides':[{'HorseSize':'','HorseName':''}]}".replace('\'','\"'); String responseString = makeSingleLineString(bos); System.err.println(responseString); Map<String,Object> expectedData = parseJson(expectedJSON); Map<String,Object> actualData = parseJson(responseString); assertEquals(expectedData, actualData); } @Test public void testSerialiseEntityResource() throws Exception { Metadata vocab = createMockRiderVocabMetadata(); ResourceState initial = new ResourceState("Riders", "initial", new ArrayList<Action>(), "/riders"); ResourceState rider = new ResourceState("Riders", "rider", new ArrayList<Action>(), "/riders/{id}"); initial.addTransition(new Transition.Builder().method("GET").target(rider).build()); ResourceStateMachine sm = new ResourceStateMachine(initial); HALProvider hp = new HALProvider(vocab, new DefaultResourceStateProvider(sm)); UriInfo mockUriInfo = mock(UriInfo.class); when(mockUriInfo.getBaseUri()).thenReturn(new URI("http://www.temenos.com/rest.svc")); when(mockUriInfo.getPath()).thenReturn("/riders/123"); hp.setUriInfo(mockUriInfo); Request requestContext = mock(Request.class); when(requestContext.getMethod()).thenReturn("GET"); hp.setRequestContext(requestContext); String inputContent = "{'_links':{'self':{'href':'http://www.temenos.com/rest.svc/riders/123'}},'age':'13','name':'Huw','rides':[{'HorseSize':'14.1','HorseName':'Molly'}]}}".replace('\'','\"'); InputStream entityStream = new ByteArrayInputStream(inputContent.getBytes()); GenericEntity<EntityResource<Entity>> ge = new GenericEntity<EntityResource<Entity>>(new EntityResource<Entity>()) {}; EntityResource<Entity> er = (EntityResource<Entity>) hp.readFrom(RESTResource.class, ge.getType(), null, MediaType.APPLICATION_HAL_JSON_TYPE, null, entityStream); er.setEntityName("Riders"); ByteArrayOutputStream bos = new ByteArrayOutputStream(); hp.writeTo(er, EntityResource.class, Entity.class, null, MediaType.APPLICATION_HAL_JSON_TYPE, null, bos); String expectedJSON = "{'_links':{'self':{'href':'http://www.temenos.com/rest.svc'}},'age':13,'name':'Huw','rides':[{'HorseSize':'14.1','HorseName':'Molly'}]}".replace('\'','\"'); String responseString = makeSingleLineString(bos); System.err.println(responseString); Map<String,Object> expectedData = parseJson(expectedJSON); Map<String,Object> actualData = parseJson(responseString); assertEquals(expectedData, actualData); } @Test public void testSerialiseEntityCollection() throws Exception { Metadata vocab = createMockRiderVocabMetadata(); ResourceState initial = new ResourceState("Riders", "initial", new ArrayList<Action>(), "/riders"); ResourceState rider = new ResourceState("Riders", "rider", new ArrayList<Action>(), "/riders/{id}"); initial.addTransition(new Transition.Builder().method("GET").target(rider).build()); ResourceStateMachine sm = new ResourceStateMachine(initial); HALProvider hp = new HALProvider(vocab, new DefaultResourceStateProvider(sm)); UriInfo mockUriInfo = mock(UriInfo.class); when(mockUriInfo.getBaseUri()).thenReturn(new URI("http://www.temenos.com/rest.svc")); when(mockUriInfo.getPath()).thenReturn("/riders/123"); hp.setUriInfo(mockUriInfo); Request requestContext = mock(Request.class); when(requestContext.getMethod()).thenReturn("GET"); hp.setRequestContext(requestContext); String inputContent = "{'_links':{'self':{'href':'http://www.temenos.com/rest.svc/riders/123'}},'age':'13','name':'Huw','rides':[{'HorseSize':'14.1','HorseName':'Molly'}]}}".replace('\'','\"'); InputStream entityStream = new ByteArrayInputStream(inputContent.getBytes()); GenericEntity<EntityResource<Entity>> ge = new GenericEntity<EntityResource<Entity>>(new EntityResource<Entity>()) {}; EntityResource<Entity> er = (EntityResource<Entity>) hp.readFrom(RESTResource.class, ge.getType(), null, MediaType.APPLICATION_HAL_JSON_TYPE, null, entityStream); er.setEntityName("Riders"); //wrap this entity in a collection CollectionResource<Entity> collection = new CollectionResource<Entity>("Riders", Collections.singletonList(er)); ByteArrayOutputStream bos = new ByteArrayOutputStream(); hp.writeTo(collection, CollectionResource.class, Entity.class, null, MediaType.APPLICATION_HAL_JSON_TYPE, null, bos); //String expectedJSON = "{'_links':{'self':{'href':'http://www.temenos.com/rest.svc/'}},'age':13,'name':'Huw','rides':[{'HorseSize':'14.1','HorseName':'Molly'}]}".replace('\'','\"'); String expectedJSON = "{'_links':{'self':{'href':'http://www.temenos.com/rest.svc'}},'_embedded':{'item':[{'age':13, 'name':'Huw', 'rides':[{'HorseSize':'14.1', 'HorseName':'Molly'}]}]}}".replace('\'','\"'); String responseString = makeSingleLineString(bos); System.err.println(responseString); Map<String,Object> expectedData = parseJson(expectedJSON); Map<String,Object> actualData = parseJson(responseString); assertEquals(expectedData, actualData); } }