/*
 * Copyright 2013 The Apache Software Foundation.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.olingo.odata2.annotation.processor.core.datasource;

import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import org.apache.olingo.odata2.annotation.processor.core.datasource.DataSource.BinaryData;
import org.apache.olingo.odata2.annotation.processor.core.edm.AnnotationEdmProvider;
import org.apache.olingo.odata2.annotation.processor.core.model.Building;
import org.apache.olingo.odata2.annotation.processor.core.model.City;
import org.apache.olingo.odata2.annotation.processor.core.model.Employee;
import org.apache.olingo.odata2.annotation.processor.core.model.GuidKeyEntity;
import org.apache.olingo.odata2.annotation.processor.core.model.Location;
import org.apache.olingo.odata2.annotation.processor.core.model.Manager;
import org.apache.olingo.odata2.annotation.processor.core.model.ModelSharedConstants;
import org.apache.olingo.odata2.annotation.processor.core.model.Photo;
import org.apache.olingo.odata2.annotation.processor.core.model.RefBase;
import org.apache.olingo.odata2.annotation.processor.core.model.Room;
import org.apache.olingo.odata2.annotation.processor.core.model.Team;
import org.apache.olingo.odata2.annotation.processor.core.util.AnnotationHelper;
import org.apache.olingo.odata2.annotation.processor.core.util.AnnotationRuntimeException;
import org.apache.olingo.odata2.api.annotation.edm.EdmKey;
import org.apache.olingo.odata2.api.annotation.edm.EdmProperty;
import org.apache.olingo.odata2.api.edm.EdmEntitySet;
import org.apache.olingo.odata2.api.edm.EdmEntityType;
import org.apache.olingo.odata2.api.edm.FullQualifiedName;
import org.apache.olingo.odata2.api.edm.provider.EntitySet;
import org.apache.olingo.odata2.api.exception.ODataException;
import org.apache.olingo.odata2.api.exception.ODataNotFoundException;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import org.mockito.Mockito;

/**
 *
 */
public class AnnotationsInMemoryDsTest {

  final static Set<Class<?>> ANNOTATED_MODEL_CLASSES = new HashSet<Class<?>>();
  final static Set<Class<?>> ANNOTATED_ENTITY_SET_CLASSES = new HashSet<Class<?>>();

  static {
    ANNOTATED_ENTITY_SET_CLASSES.add(Building.class);
    ANNOTATED_ENTITY_SET_CLASSES.add(Employee.class);
    ANNOTATED_ENTITY_SET_CLASSES.add(Manager.class);
    ANNOTATED_ENTITY_SET_CLASSES.add(Photo.class);
    ANNOTATED_ENTITY_SET_CLASSES.add(Room.class);
    ANNOTATED_ENTITY_SET_CLASSES.add(Team.class);

    ANNOTATED_MODEL_CLASSES.addAll(ANNOTATED_ENTITY_SET_CLASSES);
    ANNOTATED_MODEL_CLASSES.add(Location.class);
    ANNOTATED_MODEL_CLASSES.add(City.class);
    ANNOTATED_MODEL_CLASSES.add(RefBase.class);
  }

  private final AnnotationInMemoryDs datasource;
  private final AnnotationEdmProvider edmProvider;
  private static final String DEFAULT_CONTAINER = ModelSharedConstants.CONTAINER_1;

  public AnnotationsInMemoryDsTest() throws ODataException {
    datasource = new AnnotationInMemoryDs(Building.class.getPackage().getName(), false);
    edmProvider = new AnnotationEdmProvider(Building.class.getPackage().getName());
  }

  @Test
  public void initFromPackage() throws Exception {
    AnnotationInMemoryDs ds = new AnnotationInMemoryDs(Building.class.getPackage().getName(), false);
    Assert.assertNotNull(ds);
  }

  @Test(expected = IllegalArgumentException.class)
  public void initFromNotExistingPackage() throws Exception {
    AnnotationInMemoryDs ds = new AnnotationInMemoryDs("does.not.exist", false);
    Assert.assertNotNull(ds);
  }

  @Test
  public void initFromPackageWithoutAnnotatedClasses() throws Exception {
    AnnotationInMemoryDs ds = new AnnotationInMemoryDs(this.getClass().getPackage().getName(), false);
    Assert.assertNotNull(ds);
  }

  @Test
  public void initFromClassCollectionEntitySets() throws Exception {
    AnnotationInMemoryDs ds = new AnnotationInMemoryDs(ANNOTATED_ENTITY_SET_CLASSES, false);
    Assert.assertNotNull(ds);
  }

  @Test
  public void initFromClassCollectionModel() throws Exception {
    AnnotationInMemoryDs ds = new AnnotationInMemoryDs(ANNOTATED_MODEL_CLASSES, false);
    Assert.assertNotNull(ds);
  }

  @Test(expected = ODataException.class)
  public void initFromClassCollectionNotAnnotated() throws Exception {
    Set<Class<?>> annotatedClassesAndMore = new HashSet<Class<?>>(ANNOTATED_ENTITY_SET_CLASSES);
    annotatedClassesAndMore.add(String.class);
    annotatedClassesAndMore.add(Object.class);
    AnnotationInMemoryDs ds = new AnnotationInMemoryDs(annotatedClassesAndMore, false);
    Assert.assertNotNull(ds);
  }

  @Test
  @Ignore
  public void multiThreadedSyncOnBuildingsTest() throws Exception {
    final EdmEntitySet edmEntitySet = createMockedEdmEntitySet("Buildings");
    CountDownLatch latch;

    List<Thread> threads = new ArrayList<Thread>();
    int max = 500;

    latch = new CountDownLatch(max);
    for (int i = 0; i < max; i++) {
      threads.add(createBuildingThread(latch, datasource, edmEntitySet, String.valueOf("10")));
    }

    for (Thread thread : threads) {
      thread.start();
    }

    latch.await(60, TimeUnit.SECONDS);

    DataStore<Building> ds = datasource.getDataStore(Building.class);
    Collection<Building> buildings = ds.read();
    Assert.assertEquals(max, buildings.size());
  }

  @org.apache.olingo.odata2.api.annotation.edm.EdmEntitySet
  @org.apache.olingo.odata2.api.annotation.edm.EdmEntityType
  private static class SimpleEntity {

    @EdmKey
    @EdmProperty
    public Integer id;
    @EdmProperty
    public String name;
  }

  @Test
  @Ignore
  public void multiThreadedSyncCreateReadTest() throws Exception {
    Collection<Class<?>> ac = new ArrayList<Class<?>>();
    ac.add(SimpleEntity.class);
    final AnnotationInMemoryDs localDs = new AnnotationInMemoryDs(SimpleEntity.class.getPackage().getName(), true);
    final AnnotationEdmProvider localProvider = new AnnotationEdmProvider(ac);
    final EdmEntitySet edmEntitySet = createMockedEdmEntitySet(localProvider, "SimpleEntitySet");
    final CountDownLatch latch;

    List<Thread> threads = new ArrayList<Thread>();
    int max = 500;
    latch = new CountDownLatch(max);
    for (int i = 0; i < max; i++) {
      Runnable run = new Runnable() {
        @Override
        public void run() {
          SimpleEntity se = new SimpleEntity();
          se.id = Integer.valueOf(String.valueOf(System.currentTimeMillis()).substring(8));
          se.name = "Name: " + System.currentTimeMillis();
          try {
            localDs.createData(edmEntitySet, se);
          } catch (Exception ex) {
            throw new RuntimeException(ex);
          } finally {
            latch.countDown();
          }
        }
      };

      threads.add(new Thread(run));
    }

    for (Thread thread : threads) {
      thread.start();
    }

    latch.await(60, TimeUnit.SECONDS);

    DataStore<SimpleEntity> ds = localDs.getDataStore(SimpleEntity.class);
    Collection<SimpleEntity> buildings = ds.read();
    Assert.assertEquals(max, buildings.size());
  }

  private Thread createBuildingThread(final CountDownLatch latch, final DataSource datasource,
      final EdmEntitySet edmEntitySet, final String id) {
    Runnable run = new Runnable() {
      @Override
      public void run() {
        Building building = new Building();
        building.setName("Common Building - " + System.currentTimeMillis());
        building.setId(id);
        try {
          datasource.createData(edmEntitySet, building);
        } catch (Exception ex) {
          ex.printStackTrace();
          throw new RuntimeException(ex);
        } finally {
          latch.countDown();
        }
      }
    };

    return new Thread(run);
  }

  @Test
  public void readBinaryData() throws Exception {
    EdmEntitySet entitySet = createMockedEdmEntitySet("Photos");

    DataStore<Photo> photoDataStore = datasource.getDataStore(Photo.class);
    Photo photo = new Photo();
    photo.setName("SomePic");
    photo.setType("PNG");
    byte[] image = "binary".getBytes(Charset.defaultCharset());
    photo.setImage(image);
    photo.setImageType("image/png");
    photoDataStore.create(photo);

    Map<String, Object> keys = new HashMap<String, Object>();
    keys.put("Name", "SomePic");
    keys.put("ImageFormat", "PNG");
    Photo toReadPhoto = (Photo) datasource.readData(entitySet, keys);

    // execute
    BinaryData readBinaryData = datasource.readBinaryData(entitySet, toReadPhoto);

    // validate
    Assert.assertEquals("binary", new String(readBinaryData.getData(), Charset.defaultCharset()));
    Assert.assertArrayEquals(image, readBinaryData.getData());
    Assert.assertEquals("image/png", readBinaryData.getMimeType());
  }

  @Test
  public void readBinaryDataDirect() throws Exception {
    EdmEntitySet entitySet = createMockedEdmEntitySet("Photos");

    DataStore<Photo> photoDataStore = datasource.getDataStore(Photo.class);
    Photo photo = new Photo();
    photo.setName("SomePic");
    photo.setType("PNG");
    byte[] image = "binary".getBytes(Charset.defaultCharset());
    photo.setImage(image);
    photo.setImageType("image/png");
    photoDataStore.create(photo);

    Photo toReadPhoto = new Photo();
    toReadPhoto.setName("SomePic");
    toReadPhoto.setType("PNG");
    toReadPhoto.setImage(null);
    toReadPhoto.setImageType(null);

    BinaryData readBinaryData = datasource.readBinaryData(entitySet, toReadPhoto);

    Assert.assertEquals("binary", new String(readBinaryData.getData(), Charset.defaultCharset()));
    Assert.assertArrayEquals(image, readBinaryData.getData());
    Assert.assertEquals("image/png", readBinaryData.getMimeType());
  }

  @Test
  public void writeBinaryData() throws Exception {
    EdmEntitySet entitySet = createMockedEdmEntitySet("Photos");

    DataStore<Photo> photoDataStore = datasource.getDataStore(Photo.class);

    Photo toWritePhoto = new Photo();
    toWritePhoto.setName("SomePic");
    toWritePhoto.setType("PNG");
    photoDataStore.create(toWritePhoto);
    byte[] image = "binary".getBytes(Charset.defaultCharset());
    String mimeType = "image/png";
    BinaryData writeBinaryData = new BinaryData(image, mimeType);
    // execute
    datasource.writeBinaryData(entitySet, toWritePhoto, writeBinaryData);

    // validate
    Photo photoKey = new Photo();
    photoKey.setName("SomePic");
    photoKey.setType("PNG");
    Photo storedPhoto = photoDataStore.read(photoKey);
    Assert.assertEquals("binary", new String(storedPhoto.getImage(), Charset.defaultCharset()));
    Assert.assertArrayEquals(image, storedPhoto.getImage());
    Assert.assertEquals("image/png", storedPhoto.getImageType());
  }

  @Test(expected = ODataNotFoundException.class)
  public void writeBinaryDataNotFound() throws Exception {
    EdmEntitySet entitySet = createMockedEdmEntitySet("Photos");

    Photo toWritePhoto = new Photo();
    toWritePhoto.setName("SomePic");
    toWritePhoto.setType("PNG");
    byte[] image = "binary".getBytes(Charset.defaultCharset());
    String mimeType = "image/png";
    BinaryData writeBinaryData = new BinaryData(image, mimeType);
    // execute
    datasource.writeBinaryData(entitySet, toWritePhoto, writeBinaryData);
  }

  @Test
  public void newDataObject() throws Exception {
    EdmEntitySet roomsEntitySet = createMockedEdmEntitySet("Rooms");
    Room room = (Room) datasource.newDataObject(roomsEntitySet);

    Assert.assertNotNull(room);
  }

  @Test
  public void readEntity() throws Exception {
    EdmEntitySet buildingsEntitySet = createMockedEdmEntitySet("Buildings");
    EdmEntitySet roomsEntitySet = createMockedEdmEntitySet("Rooms");

    Building building = new Building();
    building.setName("Common Building");

    final int roomsCount = 3;
    List<Room> rooms = new ArrayList<Room>();
    for (int i = 0; i < roomsCount; i++) {
      Room room = new Room(i, "Room " + i);
      room.setBuilding(building);
      datasource.createData(roomsEntitySet, room);
      rooms.add(room);
    }

    building.getRooms().addAll(rooms);
    datasource.createData(buildingsEntitySet, building);

    Map<String, Object> keys = new HashMap<String, Object>();
    keys.put("Id", "1");

    // execute
    Object relatedData = datasource.readData(buildingsEntitySet, keys);

    // validate
    Building readBuilding = (Building) relatedData;
    Assert.assertEquals("Common Building", readBuilding.getName());
    Assert.assertEquals("1", readBuilding.getId());

    Collection<Room> relatedRooms = readBuilding.getRooms();
    Assert.assertEquals(roomsCount, relatedRooms.size());
    for (Room room : relatedRooms) {
      Assert.assertNotNull(room.getId());
      Assert.assertTrue(room.getName().matches("Room \\d*"));
      Assert.assertEquals("Common Building", room.getBuilding().getName());
    }
  }

  @Test(expected = AnnotationRuntimeException.class)
  public void readUnknownEntity() throws Exception {
    EdmEntitySet unknownEntitySet = Mockito.mock(EdmEntitySet.class);
    Mockito.when(unknownEntitySet.getName()).thenReturn("UnknownEntity");
    Map<String, Object> keys = new HashMap<String, Object>();
    keys.put("Id", "1");

    // execute
    datasource.readData(unknownEntitySet, keys);
  }

  @Test(expected = AnnotationRuntimeException.class)
  public void readUnknownEntities() throws Exception {
    EdmEntitySet unknownEntitySet = Mockito.mock(EdmEntitySet.class);
    Mockito.when(unknownEntitySet.getName()).thenReturn("UnknownEntity");

    // execute
    datasource.readData(unknownEntitySet);
  }

  @Test
  public void readEntities() throws Exception {
    EdmEntitySet roomsEntitySet = createMockedEdmEntitySet("Rooms");

    Building building = new Building();
    building.setName("Common Building");

    final int roomsCount = 11;
    List<Room> rooms = new ArrayList<Room>();
    for (int i = 0; i < roomsCount; i++) {
      Room room = new Room(i, "Room " + i);
      room.setBuilding(building);
      datasource.createData(roomsEntitySet, room);
      rooms.add(room);
    }

    // execute
    Object relatedData = datasource.readData(roomsEntitySet);

    // validate
    @SuppressWarnings("unchecked")
    Collection<Room> relatedRooms = (Collection<Room>) relatedData;
    Assert.assertEquals(roomsCount, relatedRooms.size());
    for (Room room : relatedRooms) {
      Assert.assertNotNull(room.getId());
      Assert.assertTrue(room.getName().matches("Room \\d*"));
      Assert.assertEquals("Common Building", room.getBuilding().getName());
    }
  }

  @Test
  @SuppressWarnings("unchecked")
  public void readRelatedEntities() throws Exception {
    EdmEntitySet buildingsEntitySet = createMockedEdmEntitySet("Buildings");
    EdmEntitySet roomsEntitySet = createMockedEdmEntitySet("Rooms");

    Building building = new Building();
    building.setName("Common Building");

    final int roomsCount = 10;
    List<Room> rooms = new ArrayList<Room>();
    for (int i = 0; i < roomsCount; i++) {
      Room room = new Room(i, "Room " + i);
      room.setBuilding(building);
      datasource.createData(roomsEntitySet, room);
      rooms.add(room);
    }

    building.getRooms().addAll(rooms);
    datasource.createData(buildingsEntitySet, building);

    Map<String, Object> keys = new HashMap<String, Object>();
    keys.put("Id", "1");

    Building read = (Building) datasource.readData(buildingsEntitySet, keys);
    Assert.assertEquals("Common Building", read.getName());
    Assert.assertEquals("1", read.getId());

    // execute
    Object relatedData = datasource.readRelatedData(
        buildingsEntitySet, building, roomsEntitySet, Collections.EMPTY_MAP);

    // validate
    Assert.assertTrue("Result is no collection.", relatedData instanceof Collection);
    Collection<Room> relatedRooms = (Collection<Room>) relatedData;
    Assert.assertEquals(roomsCount, relatedRooms.size());
    for (Room room : relatedRooms) {
      Assert.assertNotNull(room.getId());
      Assert.assertTrue(room.getName().matches("Room \\d*"));
      Assert.assertEquals("Common Building", room.getBuilding().getName());
    }
  }

  @Test
  @SuppressWarnings("unchecked")
  public void readRelatedEntity() throws Exception {
    EdmEntitySet buildingsEntitySet = createMockedEdmEntitySet("Buildings");
    EdmEntitySet roomsEntitySet = createMockedEdmEntitySet("Rooms");

    Building building = new Building();
    building.setName("Common Building");

    Room room = new Room(12, "Room 12");
    room.setBuilding(building);
    datasource.createData(roomsEntitySet, room);
    datasource.createData(buildingsEntitySet, building);

    Map<String, Object> keys = new HashMap<String, Object>();
    keys.put("Id", Integer.valueOf(12));
    Room read = (Room) datasource.readData(roomsEntitySet, keys);
    Assert.assertEquals("Room 12", read.getName());
    Assert.assertEquals("12", read.getId());

    // execute
    Object relatedData = datasource.readRelatedData(
        roomsEntitySet, room, buildingsEntitySet, Collections.EMPTY_MAP);

    // validate
    Assert.assertTrue("Result is no room.", relatedData instanceof Building);
    Building b = (Building) relatedData;
    Assert.assertEquals("Common Building", b.getName());
  }

  @Test
  @SuppressWarnings("unchecked")
  public void readRelatedEntityWithNull() throws Exception {
    EdmEntitySet buildingsEntitySet = createMockedEdmEntitySet("Buildings");
    EdmEntitySet roomsEntitySet = createMockedEdmEntitySet("Rooms");

    Building building = new Building();
    building.setName("Common Building");
    datasource.createData(buildingsEntitySet, building);

    Room room = new Room(12, "Room 12");
    room.setBuilding(null);
    datasource.createData(roomsEntitySet, room);
    //
    Map<String, Object> keys = new HashMap<String, Object>();
    keys.put("Id", Integer.valueOf(12));
    Room read = (Room) datasource.readData(roomsEntitySet, keys);
    Assert.assertEquals("Room 12", read.getName());
    Assert.assertEquals("12", read.getId());

    // execute
    Object relatedData = datasource.readRelatedData(
        roomsEntitySet, room, buildingsEntitySet, Collections.EMPTY_MAP);

    // validate
    Assert.assertNull("Related data must be null.", relatedData);
  }

  @Test
  public void readRelatedTargetEntity() throws Exception {
    EdmEntitySet buildingsEntitySet = createMockedEdmEntitySet("Buildings");
    EdmEntitySet roomsEntitySet = createMockedEdmEntitySet("Rooms");

    Building building = new Building();
    building.setName("Common Building");

    final int roomsCount = 10;
    List<Room> rooms = new ArrayList<Room>();
    for (int i = 0; i < roomsCount; i++) {
      Room room = new Room(i, "Room " + i);
      room.setBuilding(building);
      datasource.createData(roomsEntitySet, room);
      rooms.add(room);
    }

    building.getRooms().addAll(rooms);
    datasource.createData(buildingsEntitySet, building);

    Map<String, Object> keys = new HashMap<String, Object>();
    keys.put("Id", "1");

    Building read = (Building) datasource.readData(buildingsEntitySet, keys);
    Assert.assertEquals("Common Building", read.getName());
    Assert.assertEquals("1", read.getId());

    // execute
    Map<String, Object> targetKeys = new HashMap<String, Object>();
    targetKeys.put("Id", 3);
    Object relatedData = datasource.readRelatedData(
        buildingsEntitySet, building, roomsEntitySet, targetKeys);

    // validate
    Assert.assertTrue("Result is no Room.", relatedData instanceof Room);
    Room relatedRoom = (Room) relatedData;
    Assert.assertEquals("3", relatedRoom.getId());
    Assert.assertEquals("Room 3", relatedRoom.getName());
    Assert.assertEquals("Common Building", relatedRoom.getBuilding().getName());
  }

  @Test
  public void createSimpleEntity() throws Exception {
    EdmEntitySet edmEntitySet = createMockedEdmEntitySet("Buildings");

    Building building = new Building();
    building.setName("Common Building");
    datasource.createData(edmEntitySet, building);

    Map<String, Object> keys = new HashMap<String, Object>();
    keys.put("Id", "1");

    Building read = (Building) datasource.readData(edmEntitySet, keys);
    Assert.assertEquals("Common Building", read.getName());
    Assert.assertEquals("1", read.getId());
  }

  @Test
  public void createSimpleEntityWithOwnKey() throws Exception {
    EdmEntitySet edmEntitySet = createMockedEdmEntitySet("Buildings");

    Building building = new Building();
    building.setName("Common Building");
    AnnotationHelper ah = new AnnotationHelper();
    ah.setValueForProperty(building, "Id", "42");
    datasource.createData(edmEntitySet, building);

    Map<String, Object> keys = new HashMap<String, Object>();
    keys.put("Id", "42");

    Building read = (Building) datasource.readData(edmEntitySet, keys);
    Assert.assertEquals("Common Building", read.getName());
    Assert.assertEquals("42", read.getId());
  }

  @Test
  public void createSimpleEntityWithDuplicateKey() throws Exception {
    EdmEntitySet edmEntitySet = createMockedEdmEntitySet("Buildings");
    AnnotationHelper ah = new AnnotationHelper();

    Building building = new Building();
    building.setName("Common Building");
    ah.setValueForProperty(building, "Id", "42");
    datasource.createData(edmEntitySet, building);
    //
    Building buildingDuplicate = new Building();
    buildingDuplicate.setName("Duplicate Building");
    ah.setValueForProperty(buildingDuplicate, "Id", "42");
    datasource.createData(edmEntitySet, buildingDuplicate);

    Map<String, Object> keys42 = new HashMap<String, Object>();
    keys42.put("Id", "42");
    Building read42 = (Building) datasource.readData(edmEntitySet, keys42);
    Assert.assertEquals("Common Building", read42.getName());
    Assert.assertEquals("42", read42.getId());

    Map<String, Object> keys = new HashMap<String, Object>();
    keys.put("Id", "1");
    Building read = (Building) datasource.readData(edmEntitySet, keys);
    Assert.assertEquals("Duplicate Building", read.getName());
    Assert.assertEquals("1", read.getId());
  }

  @Test
  public void createEntityTwoKeys() throws Exception {
    EdmEntitySet edmEntitySet = createMockedEdmEntitySet("Photos");

    Photo photo = new Photo();
    photo.setName("BigPicture");
    photo.setType("PNG");
    photo.setImageUri("https://localhost/image.png");
    photo.setImageType("image/png");
    datasource.createData(edmEntitySet, photo);

    Map<String, Object> keys = new HashMap<String, Object>();
    keys.put("ImageFormat", "PNG");
    keys.put("Name", "BigPicture");

    Photo read = (Photo) datasource.readData(edmEntitySet, keys);
    Assert.assertEquals("BigPicture", read.getName());
    Assert.assertEquals("PNG", read.getType());
    Assert.assertEquals("image/png", read.getImageType());
    Assert.assertEquals("https://localhost/image.png", read.getImageUri());
  }

  @Test
  public void createAndUpdateEntityTwoKeys() throws Exception {
    EdmEntitySet edmEntitySet = createMockedEdmEntitySet("Photos");

    Photo photo = new Photo();
    final String nameKeyValue = "BigPicture";
    final String typeKeyValue = "PNG";
    photo.setName(nameKeyValue);
    photo.setType(typeKeyValue);
    photo.setImageUri("https://localhost/image.png");
    photo.setImageType("image/png");
    datasource.createData(edmEntitySet, photo);

    Map<String, Object> keys = new HashMap<String, Object>();
    keys.put("Name", "BigPicture");
    keys.put("ImageFormat", "PNG");

    Photo read = (Photo) datasource.readData(edmEntitySet, keys);
    Assert.assertEquals("BigPicture", read.getName());
    Assert.assertEquals("PNG", read.getType());
    Assert.assertEquals("image/png", read.getImageType());
    Assert.assertEquals("https://localhost/image.png", read.getImageUri());

    // update
    Photo updatedPhoto = new Photo();
    updatedPhoto.setName(nameKeyValue);
    updatedPhoto.setType(typeKeyValue);
    updatedPhoto.setImageUri("https://localhost/image.jpg");
    updatedPhoto.setImageType("image/jpg");
    datasource.updateData(edmEntitySet, updatedPhoto);

    Map<String, Object> updatedKeys = new HashMap<String, Object>();
    updatedKeys.put("Name", nameKeyValue);
    updatedKeys.put("ImageFormat", typeKeyValue);

    Photo readUpdated = (Photo) datasource.readData(edmEntitySet, updatedKeys);
    Assert.assertEquals("BigPicture", readUpdated.getName());
    Assert.assertEquals("PNG", readUpdated.getType());
    Assert.assertEquals("image/jpg", readUpdated.getImageType());
    Assert.assertEquals("https://localhost/image.jpg", readUpdated.getImageUri());
  }

  @Test
  public void createTwoKeyEntityWithOneKeyAlreadySet() throws Exception {
    EdmEntitySet edmEntitySet = createMockedEdmEntitySet("Photos");

    final String typeKeyValue = "PNG";
    final String automaticNameKeyValue = "1";

    Photo photo = new Photo();
    photo.setType(typeKeyValue);
    photo.setImageUri("https://localhost/big_picture.png");
    photo.setImageType("image/png");
    datasource.createData(edmEntitySet, photo);

    List<?> photos = datasource.readData(edmEntitySet);
    Photo readPhoto = (Photo) photos.get(0);
    Assert.assertEquals(automaticNameKeyValue + ":" + typeKeyValue,
            readPhoto.getName() + ":" + readPhoto.getType());
  }

  @Test
  public void ensureTwoKeyEntityKeysAreUnique() throws Exception {
    EdmEntitySet edmEntitySet = createMockedEdmEntitySet("Photos");

    final String nameKeyName = "Name";
    final String typeKeyName = "ImageFormat";
    final String nameKeyValue = "Big Picture";
    final String typeKeyValue = "PNG";

    final String generatedValue1 = "1";
    final String generatedValue2 = "2";

    final String imageUri1 = "https://localhost/big_picture.png";
    final String imageUri2 = "https://localhost/bigger_picture.png";

    // Add photo 1
    Photo photo1 = new Photo();
    photo1.setName(nameKeyValue);
    photo1.setType(typeKeyValue);
    photo1.setImageUri(imageUri1);
    photo1.setImageType("image/png");
    datasource.createData(edmEntitySet, photo1);

    // Add photo 2 (same key values)
    Photo photo2 = new Photo();
    photo2.setName(nameKeyValue);
    photo2.setType(typeKeyValue);
    photo2.setImageUri("https://localhost/bigger_picture.png");
    photo2.setImageType("image/png");
    datasource.createData(edmEntitySet, photo2);

    List<?> photos = datasource.readData(edmEntitySet);
    Assert.assertEquals(2, photos.size());

    // Check photo 1 has specified key values.
    Map<String, Object> keys1 = new HashMap<String, Object>();
    keys1.put(nameKeyName, nameKeyValue);
    keys1.put(typeKeyName, typeKeyValue);
    Photo readPhoto = (Photo) datasource.readData(edmEntitySet, keys1);
    Assert.assertEquals(imageUri1, readPhoto.getImageUri());

    // Check photo 2 has generated key values.
    Map<String, Object> keys2 = new HashMap<String, Object>();
    keys2.put(nameKeyName, generatedValue1);
    keys2.put(typeKeyName, generatedValue2);
    Photo readPhoto2 = (Photo) datasource.readData(edmEntitySet, keys2);
    Assert.assertEquals(imageUri2, readPhoto2.getImageUri());
  }

  @Test
  public void createGuidKeyEntity() throws Exception {
    EdmEntitySet edmEntitySet = createMockedEdmEntitySet(GuidKeyEntity.GUID_KEY_ENTITIES);

    final String entityName = "Entity name";
    GuidKeyEntity testEntity = new GuidKeyEntity();
    testEntity.setName(entityName);
    datasource.createData(edmEntitySet, testEntity);

    List<?> entities = datasource.readData(edmEntitySet);
    GuidKeyEntity readEntity = (GuidKeyEntity) entities.get(0);
    Assert.assertEquals(entityName, readEntity.getName());
  }

  @Test
  public void createGuidKeyEntityWithOwnKey() throws Exception {
    EdmEntitySet edmEntitySet = createMockedEdmEntitySet(GuidKeyEntity.GUID_KEY_ENTITIES);

    final UUID entityId = UUID.randomUUID();
    final String entityName = "Entity name";
    GuidKeyEntity testEntity = new GuidKeyEntity();
    testEntity.setId(entityId);
    testEntity.setName(entityName);
    datasource.createData(edmEntitySet, testEntity);

    Map<String, Object> keys = new HashMap<String, Object>();
    keys.put("Id", entityId);

    GuidKeyEntity readEntity = (GuidKeyEntity) datasource.readData(edmEntitySet, keys);
    Assert.assertEquals(entityId, readEntity.getId());
    Assert.assertEquals(entityName, readEntity.getName());
  }

  @Test
  public void deleteSimpleEntity() throws Exception {
    EdmEntitySet edmEntitySet = createMockedEdmEntitySet("Buildings");
    DataStore<Building> datastore = datasource.getDataStore(Building.class);

    Building building = new Building();
    building.setName("Common Building");
    datastore.create(building);

    Map<String, Object> keys = new HashMap<String, Object>();
    keys.put("Id", "1");

    Building read = (Building) datasource.readData(edmEntitySet, keys);
    Assert.assertEquals("Common Building", read.getName());
    Assert.assertEquals("1", read.getId());

    //
    datasource.deleteData(edmEntitySet, keys);

    // validate
    try {
      Building readAfterDelete = (Building) datasource.readData(edmEntitySet, keys);
      Assert.fail("Expected " + ODataNotFoundException.class + "was not thrown for '" + readAfterDelete + "'.");
    } catch (ODataNotFoundException e) {}
  }

  @Test(expected = AnnotationRuntimeException.class)
  public void unknownEntitySetForEntity() throws Exception {
    String entitySetName = "Unknown";
    FullQualifiedName entityType = new FullQualifiedName(DEFAULT_CONTAINER, entitySetName);

    EdmEntitySet edmEntitySet = Mockito.mock(EdmEntitySet.class);
    Mockito.when(edmEntitySet.getName()).thenReturn(entitySetName);
    EdmEntityType edmEntityType = Mockito.mock(EdmEntityType.class);
    Mockito.when(edmEntitySet.getEntityType()).thenReturn(edmEntityType);
    Mockito.when(edmEntityType.getName()).thenReturn(entityType.getName());

    Map<String, Object> keys = new HashMap<String, Object>();
    keys.put("Id", "1");
    //
    datasource.readData(edmEntitySet, keys);
  }

  @Test(expected = AnnotationRuntimeException.class)
  public void unknownEntitySetForEntities() throws Exception {
    String entitySetName = "Unknown";
    FullQualifiedName entityType = new FullQualifiedName(DEFAULT_CONTAINER, entitySetName);

    EdmEntitySet edmEntitySet = Mockito.mock(EdmEntitySet.class);
    Mockito.when(edmEntitySet.getName()).thenReturn(entitySetName);
    EdmEntityType edmEntityType = Mockito.mock(EdmEntityType.class);
    Mockito.when(edmEntitySet.getEntityType()).thenReturn(edmEntityType);
    Mockito.when(edmEntityType.getName()).thenReturn(entityType.getName());

    //
    datasource.readData(edmEntitySet);
  }

  @Test
  public void writeRelations() throws Exception {
    DataStore<Building> buildingStore = DataStore.createInMemory(Building.class, true);
    DataStore<Room> roomStore = DataStore.createInMemory(Room.class, true);

    EdmEntitySet buildingsEntitySet = createMockedEdmEntitySet("Buildings");
    EdmEntitySet roomsEntitySet = createMockedEdmEntitySet("Rooms");

    Building building = new Building();
    building.setName("Common Building");
    Building created = buildingStore.create(building);

    Room room = new Room(42, "Room with Number");
    room.setSeats(123);
    ;
    room.setVersion(4711);
    roomStore.create(room);

    Map<String, Object> targetEntityKeyValues = new HashMap<String, Object>();
    targetEntityKeyValues.put("Id", 42);

    // execute
    datasource.writeRelation(buildingsEntitySet, building, roomsEntitySet, targetEntityKeyValues);

    // validate
    Building readBuilding = buildingStore.read(created);
    Room readRoom = roomStore.read(new Room(42, ""));

    List<Room> readRooms = readBuilding.getRooms();
    Assert.assertEquals(1, readRooms.size());
    Assert.assertEquals(readRoom, readRooms.get(0));

    Assert.assertEquals("42", readRoom.getId());
    Assert.assertEquals(123, readRoom.getSeats());
    Assert.assertEquals(4711, readRoom.getVersion());
    Assert.assertEquals(readBuilding, readRoom.getBuilding());
  }

  private EdmEntitySet createMockedEdmEntitySet(final String entitySetName) throws ODataException {
    return createMockedEdmEntitySet(edmProvider, entitySetName);
  }

  private EdmEntitySet createMockedEdmEntitySet(final AnnotationEdmProvider edmProvider, final String entitySetName)
      throws ODataException {
    EntitySet entitySet = edmProvider.getEntitySet(DEFAULT_CONTAINER, entitySetName);
    FullQualifiedName entityType = entitySet.getEntityType();

    EdmEntitySet edmEntitySet = Mockito.mock(EdmEntitySet.class);
    Mockito.when(edmEntitySet.getName()).thenReturn(entitySetName);
    EdmEntityType edmEntityType = Mockito.mock(EdmEntityType.class);
    Mockito.when(edmEntitySet.getEntityType()).thenReturn(edmEntityType);
    Mockito.when(edmEntityType.getName()).thenReturn(entityType.getName());

    return edmEntitySet;
  }
}
