public final class ReadOptions {
  private FilterExpression filter;
  private OrderByExpression orderBy;
  private String skipToken;
  private Integer skip;
  private Integer top;

  /**
   * None read options.
   *
   * @return the read options
   */
  public static ReadOptions none() {
    return new ReadOptions();
  }

  /**
   * Start builder.
   *
   * @return the builder
   */
  public static Builder start() {
    return new Builder();
  }

  /**
   * Start builder.
   *
   * @param filter the filterApplied
   * @return the builder
   */
  public static Builder start(FilterExpression filter) {
    return new Builder().filter(filter);
  }

  /**
   * Start builder.
   *
   * @param orderBy the orderApplied by
   * @return the builder
   */
  public static Builder start(OrderByExpression orderBy) {
    return new Builder().order(orderBy);
  }

  /**
   * Start builder.
   *
   * @param skipToken the skipApplied token
   * @param skip the skipApplied
   * @return the builder
   */
  public static Builder start(String skipToken, Integer skip) {
    return new Builder().skip(skipToken, skip);
  }

  /**
   * Start builder.
   *
   * @param top the topApplied
   * @return the builder
   */
  public static Builder start(Integer top) {
    return new Builder().top(top);
  }

  /**
   * Gets filterApplied.
   *
   * @return the filterApplied
   */
  public FilterExpression getFilter() {
    return filter;
  }

  /**
   * Gets orderApplied by.
   *
   * @return the orderApplied by
   */
  public OrderByExpression getOrderBy() {
    return orderBy;
  }

  /**
   * Gets skipApplied.
   *
   * @return the skipApplied
   */
  public Integer getSkip() {
    return skip;
  }

  /**
   * Gets skipApplied token.
   *
   * @return the skipApplied token
   */
  public String getSkipToken() {
    return skipToken;
  }

  /**
   * Gets topApplied.
   *
   * @return the topApplied
   */
  public Integer getTop() {
    return top;
  }

  /**
   * The type Builder.
   */
  public static class Builder {
    private ReadOptions options = new ReadOptions();

    /**
     * Build read options.
     *
     * @return the read options
     */
    public ReadOptions build() {
      return options;
    }

    /**
     * Filter builder.
     *
     * @param filter the filterApplied
     * @return the builder
     */
    public Builder filter(FilterExpression filter) {
      options.filter = filter;
      return this;
    }

    /**
     * Order builder.
     *
     * @param orderBy the orderApplied by
     * @return the builder
     */
    public Builder order(OrderByExpression orderBy) {
      options.orderBy = orderBy;
      return this;
    }

    /**
     * Skip builder.
     *
     * @param skipToken the skipApplied token
     * @param skip the skipApplied
     * @return the builder
     */
    public Builder skip(String skipToken, Integer skip) {
      options.skipToken = skipToken;
      options.skip = skip;
      return this;
    }

    /**
     * Top builder.
     *
     * @param top the topApplied
     * @return the builder
     */
    public Builder top(Integer top) {
      options.top = top;
      return this;
    }
  }
} public final class ReadResult<T> {
  private final Collection<T> result;
  private boolean appliedSkip = false;
  private boolean appliedTop = false;
  private boolean appliedOrder = false;
  private boolean appliedFilter = false;

  private ReadResult(Collection<T> result) {
    this.result = new ArrayList<>(result);
  }

  /**
   * Gets result.
   *
   * @return the result
   */
  public Collection<T> getResult() {
    return Collections.unmodifiableCollection(result);
  }

  /**
   * Get first result from results collection
   * or <code>null</code> if result collection is empty.
   *
   * @return first result from results collection
   *          or <code>null</code> if result collection is empty.
   */
  public T getFirst() {
    if(result.isEmpty()) {
      return null;
    }
    return result.iterator().next();
  }

  /**
   * Applied skipApplied.
   *
   * @return the boolean
   */
  public boolean isSkipApplied() {
    return appliedSkip;
  }

  /**
   * Applied topApplied.
   *
   * @return the boolean
   */
  public boolean isTopApplied() {
    return appliedTop;
  }

  /**
   * Applied orderApplied.
   *
   * @return the boolean
   */
  public boolean isOrderApplied() {
    return appliedOrder;
  }

  /**
   * Applied filterApplied.
   *
   * @return the boolean
   */
  public boolean isFilterApplied() {
    return appliedFilter;
  }

  /**
   * For result.
   *
   * @param <T>  the type parameter
   * @return the builder
   */
  public static <T> Builder<T> start() {
    return new Builder<>();
  }

  /**
   * For result.
   *
   * @param <T>  the type parameter
   * @param result the result
   * @return the builder
   */
  public static <T> Builder<T> forResult(Collection<T> result) {
    return new Builder<>(result);
  }

  /**
   * Start building a result with applied settings from existing result.
   *
   * @param readResult existing result
   * @param result result values
   * @param <T> type parameter for ReadResult
   * @return the new ReadResult
   */
  public static <T> Builder<T> fromResult(ReadResult<T> readResult, Collection<T> result) {
    return new Builder<>(result).apply(readResult);
  }

  /**
   * For an empty result.
   *
   * @param <T>  the type parameter
   * @return the empty read result
   */
  public static <T> ReadResult<T> empty() {
    return new Builder<T>(Collections.emptyList()).build();
  }

  /**
   * The type Builder.
   * @param <T>  the type parameter
   */
  public static class Builder<T> {
    private final ReadResult<T> readResult;

    /**
     * Instantiates a new Builder.
     */
    public Builder() {
      this(new ArrayList<>());
    }

    /**
     * Instantiates a new Builder.
     *
     * @param result the result
     */
    public Builder(Collection<T> result) {
      readResult = new ReadResult<>(result);
    }

    Builder<T> apply(ReadResult<T> result) {
      readResult.appliedFilter = result.appliedFilter;
      readResult.appliedOrder = result.appliedOrder;
      readResult.appliedSkip = result.appliedSkip;
      readResult.appliedTop = result.appliedTop;
      return this;
    }


    /**
     * Set top to applied.
     *
     * @return the builder
     */
    public Builder<T> topApplied() {
      readResult.appliedTop = true;
      return this;
    }

    /**
     * Set skip to applied.
     *
     * @return the builder
     */
    public Builder<T> skipApplied() {
      readResult.appliedSkip = true;
      return this;
    }

    /**
     * Set filter to applied.
     *
     * @return the builder
     */
    public Builder<T> filterApplied() {
      readResult.appliedFilter = true;
      return this;
    }

    /**
     * Set order to applied.
     *
     * @return the builder
     */
    public Builder<T> orderApplied() {
      readResult.appliedOrder = true;
      return this;
    }

    public Builder<T> values(Collection<T> values) {
      if(!readResult.result.isEmpty()) {
        readResult.result.clear();
      }
      readResult.result.addAll(values);
      return this;
    }

    /**
     * Build read result.
     *
     * @return the read result
     */
    public ReadResult<T> build() {
      return readResult;
    }
  }
} public interface ValueAccess {

  /**
   * Retrieves the value of an EDM property for the given data object.
   * @param data the Java data object
   * @param property the requested
   * @return the requested property value
   * @throws ODataException the o data exception
   */
  <T> Object getPropertyValue(final T data, final EdmProperty property) throws ODataException;

  /**
   * Sets the value of an EDM property for the given data object.
   * @param data the Java data object
   * @param property the
   * @param value the new value of the property
   * @throws ODataException the o data exception
   */
  <T, V> void setPropertyValue(T data, final EdmProperty property, final V value) throws ODataException;

  /**
   * Retrieves the Java type of an EDM property for the given data object.
   * @param data the Java data object
   * @param property the requested
   * @return the requested Java type
   * @throws ODataException the o data exception
   */
  <T> Class<?> getPropertyType(final T data, final EdmProperty property) throws ODataException;

  /**
   * Retrieves the value defined by a mapping object for the given data object.
   * @param data the Java data object
   * @param mapping the requested
   * @return the requested value
   * @throws ODataException the o data exception
   */
  <T> Object getMappingValue(final T data, final EdmMapping mapping) throws ODataException;

  /**
   * Sets the value defined by a mapping object for the given data object.
   * @param data the Java data object
   * @param mapping the
   * @param value the new value
   * @throws ODataException the o data exception
   */
  <T, V> void setMappingValue(T data, final EdmMapping mapping, final V value) throws ODataException;
} public interface DataSource {

  /**
   * Retrieves the whole data list for the specified entity set.
   * @param entitySet the requested
   * @param readOptions the read options
   * @return the requested data list
   * @throws ODataNotImplementedException the o data not implemented exception
   * @throws ODataNotFoundException the o data not found exception
   * @throws EdmException the edm exception
   * @throws ODataApplicationException the o data application exception
   */
  ReadResult<?> readData(EdmEntitySet entitySet, ReadOptions readOptions)
      throws ODataNotImplementedException, ODataNotFoundException, EdmException, ODataApplicationException;

  /**
   * Retrieves a single data object for the specified entity set and key.
   * @param entitySet the requested
   * @param keys the entity key as map of key names to key values
   * @return the requested data object
   * @throws ODataNotImplementedException the o data not implemented exception
   * @throws ODataNotFoundException the o data not found exception
   * @throws EdmException the edm exception
   * @throws ODataApplicationException the o data application exception
   */
  Object readData(EdmEntitySet entitySet, Map<String, Object> keys) throws ODataNotImplementedException,
      ODataNotFoundException, EdmException, ODataApplicationException;

  /**
   * <p>Retrieves related data for the specified source data, entity set, and key.</p>
   * <p>If the underlying association of the EDM is specified to have target
   * multiplicity '*' and no target key is given, this method returns a list of
   * related data, otherwise it returns a single data object.</p>
   * @param sourceEntitySet the EdmEntitySet of the source entity
   * @param sourceData the data object of the source entity
   * @param targetEntitySet the requested target
   * @param targetKeys the key of the target entity as map of key names to key values (optional)
   * @return the requested related data object, either a list or a single object
   * @throws ODataNotImplementedException the o data not implemented exception
   * @throws ODataNotFoundException the o data not found exception
   * @throws EdmException the edm exception
   * @throws ODataApplicationException the o data application exception
   */
  Object readRelatedData(EdmEntitySet sourceEntitySet, Object sourceData, EdmEntitySet targetEntitySet,
      Map<String, Object> targetKeys) throws ODataNotImplementedException, ODataNotFoundException, EdmException,
      ODataApplicationException;

  /**
   * Retrieves the binary data and the MIME type for the media resource
   * associated to the specified media-link entry.
   * @param entitySet the EdmEntitySet of the media-link entry
   * @param mediaLinkEntryData the data object of the media-link entry
   * @return the binary data and the MIME type of the media resource
   * @throws ODataNotImplementedException the o data not implemented exception
   * @throws ODataNotFoundException the o data not found exception
   * @throws EdmException the edm exception
   * @throws ODataApplicationException the o data application exception
   */
  BinaryData readBinaryData(EdmEntitySet entitySet, Object mediaLinkEntryData) throws ODataNotImplementedException,
      ODataNotFoundException, EdmException, ODataApplicationException;

  /**
   * <p>Creates and returns a new instance of the requested data-object type.</p>
   * <p>This instance must not be part of the corresponding list and should
   * have empty content, apart from the key and other mandatory properties.
   * However, intermediate objects to access complex properties must not be
   * <code>null</code>.</p>
   * @param entitySet the
   * the object must correspond to
   * @return the new data object
   * @throws ODataNotImplementedException the o data not implemented exception
   * @throws EdmException the edm exception
   * @throws ODataApplicationException the o data application exception
   */
  Object newDataObject(EdmEntitySet entitySet) throws ODataNotImplementedException, EdmException,
      ODataApplicationException;

  /**
   * Writes the binary data for the media resource associated to the
   * specified media-link entry.
   * @param entitySet the
   * of the media-link entry
   * @param mediaLinkEntryData the data object of the media-link entry
   * @param binaryData the binary data of the media resource along with
   * the MIME type of the binary data
   * @throws ODataNotImplementedException the o data not implemented exception
   * @throws ODataNotFoundException the o data not found exception
   * @throws EdmException the edm exception
   * @throws ODataApplicationException the o data application exception
   */
  void writeBinaryData(EdmEntitySet entitySet, Object mediaLinkEntryData, BinaryData binaryData)
      throws ODataNotImplementedException, ODataNotFoundException, EdmException, ODataApplicationException;

  /**
   * Deletes a single data object identified by the specified entity set and key.
   * @param entitySet the
   * of the entity to be deleted
   * @param keys the entity key as map of key names to key values
   * @throws ODataNotImplementedException the o data not implemented exception
   * @throws ODataNotFoundException the o data not found exception
   * @throws EdmException the edm exception
   * @throws ODataApplicationException the o data application exception
   */
  void deleteData(EdmEntitySet entitySet, Map<String, Object> keys) throws ODataNotImplementedException,
      ODataNotFoundException, EdmException, ODataApplicationException;

  /**
   * <p>Inserts an instance into the entity list of the specified entity set.</p>
   * <p>If {@link #newDataObject} has not set the key and other mandatory
   * properties already, this method must set them before inserting the
   * instance into the list.</p>
   *
   * @param entitySet the EdmEntitySet the object must correspond to
   * @param data the data object of the new entity
   * @return the new created object instance (which is NOT the same object as the given data parameter object)
   * @throws ODataNotImplementedException the o data not implemented exception
   * @throws EdmException the edm exception
   * @throws ODataApplicationException the o data application exception
   */
  Object createData(EdmEntitySet entitySet, Object data) throws ODataNotImplementedException, EdmException,
      ODataApplicationException;

  /**
   * Deletes the relation from the specified source data to a target entity
   * specified by entity set and key.
   * @param sourceEntitySet the EdmEntitySet of the source entity
   * @param sourceData the data object of the source entity
   * @param targetEntitySet the EdmEntitySet of the target entity
   * @param targetKeys the key of the target entity as map of key names to key values (optional)
   * @throws ODataNotImplementedException the o data not implemented exception
   * @throws ODataNotFoundException the o data not found exception
   * @throws EdmException the edm exception
   * @throws ODataApplicationException the o data application exception
   */
  void deleteRelation(EdmEntitySet sourceEntitySet, Object sourceData, EdmEntitySet targetEntitySet,
      Map<String, Object> targetKeys) throws ODataNotImplementedException, ODataNotFoundException, EdmException,
      ODataApplicationException;

  /**
   * Writes a relation from the specified source data to a target entity
   * specified by entity set and key.
   * @param sourceEntitySet the EdmEntitySet of the source entity
   * @param sourceData the data object of the source entity
   * @param targetEntitySet the EdmEntitySet of the relation target
   * @param targetKeys the key of the target entity as map of key names to key values
   * @throws ODataNotImplementedException the o data not implemented exception
   * @throws ODataNotFoundException the o data not found exception
   * @throws EdmException the edm exception
   * @throws ODataApplicationException the o data application exception
   */
  void writeRelation(EdmEntitySet sourceEntitySet, Object sourceData, EdmEntitySet targetEntitySet,
      Map<String, Object> targetKeys) throws ODataNotImplementedException, ODataNotFoundException, EdmException,
      ODataApplicationException;

  /**
   * Container to store binary data (as byte array) and the associated MIME type.
   */
  class BinaryData {
    private final byte[] data;
    private final String mimeType;

    /**
     * Instantiates a new Binary data.
     *
     * @param data the data
     * @param mimeType the mime type
     */
    public BinaryData(final byte[] data, final String mimeType) {
      this.data = data;
      this.mimeType = mimeType;
    }

    /**
     * Get data.
     *
     * @return the byte [ ]
     */
    public byte[] getData() {
      return data;
    }

    /**
     * Gets mime type.
     *
     * @return the mime type
     */
    public String getMimeType() {
      return mimeType;
    }

    @Override
    public String toString() {
      return "data=" + Arrays.toString(data) + ", mimeType=" + mimeType;
    }
  }
} public interface DataStoreManager {
  /**
   * The KEEP _ pERSISTENT.
   */
  String KEEP_PERSISTENT = "KEEP_PERSISTENT";

  /**
   * Set default properties which are used for all methods without explicit properties parameter.
   *
   * @param name name of property
   * @param value value of property
   */
  void setDefaultProperty(String name, String value);

  /**
   * Create a DataStore without managing (keeping a reference to it).
   *
   * @param <T>  type of class for which the DataStore is created
   * @param clz class for which the DataStore is created
   * @return the created (and not managed) DataStore
   * @throws DataStoreException the data store exception
   */
  <T> DataStore<T> createDataStore(Class<T> clz) throws DataStoreException;

  /**
   * Create a DataStore without managing (keeping a reference to it).
   *
   * @param <T>  type of class for which the DataStore is created
   * @param clz class for which the DataStore is created
   * @param properties properties for creation
   * @return the created (and not managed) DataStore
   * @throws DataStoreException if something goes wrong
   */
  <T> DataStore<T> createDataStore(Class<T> clz, Map<String, String> properties) throws DataStoreException;

  /**
   * Create a DataStore and managing it (keeping a reference to it).
   * If a DataStore with given name already exists this will be returned.
   *
   * @param <T>  type of class for which the DataStore is created
   * @param name unique name for the DataStore
   * @param clz class for which the DataStore is created
   * @return the created (and managed) DataStore
   * @throws DataStoreException if something goes wrong
   */
  <T> DataStore<T> grantDataStore(String name, Class<T> clz) throws DataStoreException;

  /**
   * Create a DataStore and managing it (keeping a reference to it).
   * If a DataStore with given name already exists this will be returned.
   *
   * @param <T>  type of class for which the DataStore is created
   * @param name unique name for the DataStore
   * @param clz class for which the DataStore is created
   * @param properties properties for creation
   * @return the created (and managed) DataStore
   * @throws DataStoreException if something goes wrong
   */
  <T> DataStore<T> grantDataStore(String name, Class<T> clz, Map<String, String> properties) throws DataStoreException;

  /**
   * Get a reference to an already existing (managed) DataStore.
   *
   * @param name name of the DataStore which is requested
   * @param clz class for which the DataStore is created
   * @return the created (and managed) DataStore or <code>NULL</code> if no according DataStore exists
   * @throws DataStoreException if something goes wrong
   */
  <T> DataStore<T> getDataStore(String name, Class<T> clz) throws DataStoreException;

  /**
   * Get a reference to an already existing (managed) DataStore.
   *
   * @param name name of the DataStore which is requested
   * @return the created (and managed) DataStore or <code>NULL</code> if no according DataStore exists
   */
  DataStore<Object> getDataStore(String name);
} public class AnnotationValueAccess implements ValueAccess {
  private final AnnotationHelper annotationHelper = new AnnotationHelper();

  /**
   * Retrieves the value of an EDM property for the given data object.
   * @param data the Java data object
   * @param property the requested {@link EdmProperty}
   * @return the requested property value
   */
  @Override
  public <T> Object getPropertyValue(final T data, final EdmProperty property) throws ODataException {
    if (data == null) {
      return null;
    } else if (annotationHelper.isEdmAnnotated(data)) {
      return annotationHelper.getValueForProperty(data, property.getName());
    }
    throw new ODataNotImplementedException(ODataNotImplementedException.COMMON);
  }

  /**
   * Sets the value of an EDM property for the given data object.
   * @param data the Java data object
   * @param property the {@link EdmProperty}
   * @param value the new value of the property
   */
  @Override
  public <T, V> void setPropertyValue(final T data, final EdmProperty property, final V value) throws ODataException {
    if (data != null) {
      if (annotationHelper.isEdmAnnotated(data)) {
        annotationHelper.setValueForProperty(data, property.getName(), value);
      } else {
        throw new ODataNotImplementedException(ODataNotImplementedException.COMMON);
      }
    }
  }

  /**
   * Retrieves the Java type of an EDM property for the given data object.
   * @param data the Java data object
   * @param property the requested {@link EdmProperty}
   * @return the requested Java type
   */
  @Override
  public <T> Class<?> getPropertyType(final T data, final EdmProperty property) throws ODataException {
    if (data == null) {
      return null;
    } else if (annotationHelper.isEdmAnnotated(data)) {
      Class<?> fieldType = annotationHelper.getFieldTypeForProperty(data, property.getName());
      if (fieldType == null) {
        throw new ODataException("No field type found for property " + property);
      }
      return fieldType;
    }
    throw new ODataNotImplementedException(ODataNotImplementedException.COMMON);
  }

  /**
   * Retrieves the value defined by a mapping object for the given data object.
   * @param data the Java data object
   * @param mapping the requested {@link EdmMapping}
   * @return the requested value
   */
  @Override
  public <T> Object getMappingValue(final T data, final EdmMapping mapping) throws ODataException {
    if (mapping != null && mapping.getMediaResourceMimeTypeKey() != null) {
      return annotationHelper.getValueForProperty(data, mapping.getMediaResourceMimeTypeKey());
    }
    return null;
  }

  /**
   * Sets the value defined by a mapping object for the given data object.
   * @param data the Java data object
   * @param mapping the {@link EdmMapping}
   * @param value the new value
   */
  @Override
  public <T, V> void setMappingValue(final T data, final EdmMapping mapping, final V value) throws ODataException {
    if (mapping != null && mapping.getMediaResourceMimeTypeKey() != null) {
      annotationHelper.setValueForProperty(data, mapping.getMediaResourceMimeTypeKey(), value);
    }
  }
} public class BeanPropertyAccess implements ValueAccess {

  @Override
  public <T> Object getPropertyValue(final T data, final EdmProperty property) throws ODataException {
    return getValue(data, getGetterMethodName(property));
  }

  @Override
  public <T, V> void setPropertyValue(final T data, final EdmProperty property, final V value) throws ODataException {
    final String methodName = getSetterMethodName(getGetterMethodName(property));
    if (methodName != null) {
      setValue(data, methodName, value);
    }
  }

  @Override
  public <T> Class<?> getPropertyType(final T data, final EdmProperty property) throws ODataException {
    return getType(data, getGetterMethodName(property));
  }

  @Override
  public <T> Object getMappingValue(final T data, final EdmMapping mapping) throws ODataException {
    if (mapping != null && mapping.getMediaResourceMimeTypeKey() != null) {
      return getValue(data, mapping.getMediaResourceMimeTypeKey());
    }
    return null;
  }

  @Override
  public <T, V> void setMappingValue(final T data, final EdmMapping mapping, final V value) throws ODataException {
    if (mapping != null && mapping.getMediaResourceMimeTypeKey() != null) {
      setValue(data, getSetterMethodName(mapping.getMediaResourceMimeTypeKey()), value);
    }
  }

  private String getGetterMethodName(final EdmProperty property) throws EdmException {
    final String prefix = isBooleanProperty(property) ? "is" : "get";
    final String defaultMethodName = prefix + property.getName();
    return property.getMapping() == null || property.getMapping().getInternalName() == null ?
        defaultMethodName : property.getMapping().getInternalName();
  }

  private boolean isBooleanProperty(final EdmProperty property) throws EdmException {
    return property.isSimple()
        && property.getType() == EdmSimpleTypeKind.Boolean.getEdmSimpleTypeInstance();
  }

  private String getSetterMethodName(final String getterMethodName) {
    return getterMethodName.contains(".") ?
        null : getterMethodName.replaceFirst("^is", "set").replaceFirst("^get", "set");
  }

  private <T> Object getValue(final T data, final String methodName) throws ODataNotFoundException {
    Object dataObject = data;

    for (final String method : methodName.split("\\.", -1)) {
      if (dataObject != null) {
        try {
          dataObject = dataObject.getClass().getMethod(method).invoke(dataObject);
        } catch (SecurityException | NoSuchMethodException | IllegalAccessException
            | IllegalArgumentException | InvocationTargetException e) {
          throw new ODataNotFoundException(ODataHttpException.COMMON, e);
        }
      }
    }

    return dataObject;
  }

  private <T, V> void setValue(final T data, final String methodName, final V value)
      throws ODataNotFoundException {
    try {
      boolean found = false;
      for (final Method method : Arrays.asList(data.getClass().getMethods())) {
        if (method.getName().equals(methodName)) {
          found = true;
          final Class<?> type = method.getParameterTypes()[0];
          if (value == null) {
            if (type.equals(byte.class) || type.equals(short.class) || type.equals(int.class)
                || type.equals(long.class) || type.equals(char.class)) {
              method.invoke(data, 0);
            } else if (type.equals(float.class) || type.equals(double.class)) {
              method.invoke(data, 0.0);
            } else if (type.equals(boolean.class)) {
              method.invoke(data, false);
            } else {
              method.invoke(data, new Object[]{null});
            }
          } else {
            method.invoke(data, value);
          }
          break;
        }
      }
      if (!found) {
        throw new ODataNotFoundException(null);
      }
    } catch (SecurityException | IllegalArgumentException | IllegalAccessException | InvocationTargetException e) {
      throw new ODataNotFoundException(null, e);
    }
  }

  private <T> Class<?> getType(final T data, final String methodName) throws ODataNotFoundException {
    if (data == null) {
      throw new ODataNotFoundException(ODataHttpException.COMMON);
    }

    Class<?> type = data.getClass();
    for (final String method : methodName.split("\\.", -1)) {
      try {
        type = type.getMethod(method).getReturnType();
        if (type.isPrimitive()) {
          if (type == boolean.class) {
            type = Boolean.class;
          } else if (type == byte.class) {
            type = Byte.class;
          } else if (type == short.class) {
            type = Short.class;
          } else if (type == int.class) {
            type = Integer.class;
          } else if (type == long.class) {
            type = Long.class;
          } else if (type == float.class) {
            type = Float.class;
          } else if (type == double.class) {
            type = Double.class;
          }
        }
      } catch (final SecurityException | NoSuchMethodException e) {
        throw new ODataNotFoundException(ODataHttpException.COMMON, e);
      }
    }
    return type;
  }
} public class AnnotationDataSource implements DataSource {

  private static final AnnotationHelper ANNOTATION_HELPER = new AnnotationHelper();

  private final DataStoreManager dataStoreManager;

  public AnnotationDataSource(final Collection<Class<?>> annotatedClasses, final DataStoreManager dataStoreManager)
      throws ODataException {
    this.dataStoreManager = dataStoreManager;
    
    init(annotatedClasses);
  }

  public AnnotationDataSource(final String packageToScan, final DataStoreManager dataStoreManager)
          throws ODataException {
    this.dataStoreManager = dataStoreManager;

    List<Class<?>> foundClasses = ClassHelper.loadClasses(packageToScan, ANNOTATION_HELPER::isEdmAnnotated);

    init(foundClasses);
  }

  @SuppressWarnings("unchecked")
  private void init(final Collection<Class<?>> annotatedClasses) throws ODataException {
    try {
      for (Class<?> clz : annotatedClasses) {
        String entitySetName = ANNOTATION_HELPER.extractEntitySetName(clz);
        if(entitySetName != null) {
          dataStoreManager.grantDataStore(entitySetName, clz);
        } else if (!ANNOTATION_HELPER.isEdmAnnotated(clz)) {
          throw new ODataException("Found not annotated class during DataStore initialization of type: "
              + clz.getName());
        }
      }
    } catch (DataStoreException e) {
      throw new ODataException("Error in DataStore initialization with message: " + e.getMessage(), e);
    }
  }

  @SuppressWarnings("unchecked")
  public <T> DataStore<T> getDataStore(final Class<T> clazz) throws DataStoreException {
    String entitySetName = ANNOTATION_HELPER.extractEntitySetName(clazz);
    return dataStoreManager.getDataStore(entitySetName, clazz);
  }

  @Override
  public ReadResult<?> readData(final EdmEntitySet entitySet, ReadOptions readOptions) throws ODataNotImplementedException,
      ODataNotFoundException, EdmException, ODataApplicationException {

    DataStore<Object> holder = getDataStore(entitySet);
    if (holder != null) {
      return holder.read(readOptions);
    }

    throw new ODataNotFoundException(ODataNotFoundException.ENTITY);
  }

  @Override
  public Object readData(final EdmEntitySet entitySet, final Map<String, Object> keys)
      throws ODataNotFoundException, EdmException, ODataApplicationException {

    DataStore<Object> store = getDataStore(entitySet);
    if (store != null) {
      Object keyInstance = store.createInstance();
      ANNOTATION_HELPER.setKeyFields(keyInstance, keys);

      Object result = store.read(keyInstance);
      if (result != null) {
        return result;
      }
    }

    throw new ODataNotFoundException(ODataNotFoundException.ENTITY);
  }

  @Override
  public Object readRelatedData(final EdmEntitySet sourceEntitySet, final Object sourceData,
      final EdmEntitySet targetEntitySet,
      final Map<String, Object> targetKeys)
      throws ODataNotImplementedException, ODataNotFoundException, EdmException, ODataApplicationException {

    DataStore<?> sourceStore = dataStoreManager.getDataStore(sourceEntitySet.getName());
    DataStore<?> targetStore = dataStoreManager.getDataStore(targetEntitySet.getName());

    AnnotationHelper.AnnotatedNavInfo navInfo = ANNOTATION_HELPER.getCommonNavigationInfo(
        sourceStore.getDataTypeClass(), targetStore.getDataTypeClass());
    final Field sourceField;
    if(navInfo.isBiDirectional()) {
      sourceField = navInfo.getToField();
    } else {
      sourceField = navInfo.getFromField();
    }
    if (sourceField == null) {
      throw new AnnotationRuntimeException("Missing source field for related data (sourceStore='" + sourceStore
          + "', targetStore='" + targetStore + "').");
    }

    List<Object> resultData = readResultData(targetStore, sourceData, sourceField, navInfo);
    return extractResultData(targetStore, targetKeys, navInfo, resultData);
  }

  /**
   * Read the result data from the target store based on <code>sourceData</code> and <code>sourceField</code>
   * 
   * @param targetStore
   * @param sourceData
   * @param sourceField
   * @return
   * @throws DataStoreException
   */
  private List<Object> readResultData(final DataStore<?> targetStore, final Object sourceData, 
          final Field sourceField, final AnnotationHelper.AnnotatedNavInfo navInfo)
      throws DataStoreException {
    Object navigationInstance = getValue(sourceField, sourceData);
    if (navigationInstance == null) {
      return Collections.emptyList();
    }
    
    List<Object> resultData = new ArrayList<>();
    for (Object targetInstance : targetStore.read()) {
      if (navigationInstance instanceof Collection) {
        Map<String, Object> keyName2Value = 
                ANNOTATION_HELPER.getValueForAnnotatedFields(sourceData, EdmKey.class);
        Field toField = navInfo.getToField();
        Object backInstance = ClassHelper.getFieldValue(targetInstance, toField);
        boolean keyMatch = ANNOTATION_HELPER.keyMatch(backInstance, keyName2Value);
        if(keyMatch) {
          resultData.add(targetInstance);
        }
      } else if (targetStore.isKeyEqualChecked(targetInstance, navigationInstance)) {
        resultData.add(targetInstance);
      }
    }
    return resultData;
  }

  /**
   * Extract the <code>result data</code> from the <code>resultData</code> list based on
   * <code>navigation information</code> and <code>targetKeys</code>.
   * 
   * @param targetStore
   * @param targetKeys
   * @param navInfo
   * @param resultData
   * @return
   * @throws DataStoreException
   */
  private Object extractResultData(final DataStore<?> targetStore, final Map<String, Object> targetKeys,
      final AnnotationHelper.AnnotatedNavInfo navInfo, final List<Object> resultData) throws DataStoreException {
    if (navInfo.getToMultiplicity() == EdmMultiplicity.MANY) {
      if (targetKeys.isEmpty()) {
        return resultData;
      } else {
        Object keyInstance = targetStore.createInstance();
        ANNOTATION_HELPER.setKeyFields(keyInstance, targetKeys);
        for (Object result : resultData) {
          if (targetStore.isKeyEqualChecked(result, keyInstance)) {
            return result;
          }
        }
        return null;
      }
    } else {
      if (resultData.isEmpty()) {
        return null;
      }
      return resultData.get(0);
    }
  }

  @Override
  public BinaryData readBinaryData(final EdmEntitySet entitySet, final Object mediaLinkEntryData)
      throws ODataNotImplementedException, ODataNotFoundException, EdmException, ODataApplicationException {

    Object data = ANNOTATION_HELPER.getValueForField(mediaLinkEntryData, EdmMediaResourceContent.class);
    Object mimeType = ANNOTATION_HELPER.getValueForField(mediaLinkEntryData, EdmMediaResourceMimeType.class);

    if (data == null && mimeType == null) {
      DataStore<Object> dataStore = getDataStore(entitySet);
      Object readEntry = dataStore.read(mediaLinkEntryData);
      if (readEntry != null) {
        data = ANNOTATION_HELPER.getValueForField(readEntry, EdmMediaResourceContent.class);
        mimeType = ANNOTATION_HELPER.getValueForField(readEntry, EdmMediaResourceMimeType.class);
      }
    }

    return new BinaryData((byte[]) data, String.valueOf(mimeType));
  }

  @Override
  public Object newDataObject(final EdmEntitySet entitySet)
      throws ODataNotImplementedException, EdmException, ODataApplicationException {

    DataStore<Object> dataStore = getDataStore(entitySet);
    if (dataStore != null) {
      return dataStore.createInstance();
    }

    throw new AnnotationRuntimeException("No DataStore found for entitySet with name: " + entitySet.getName());
  }

  @Override
  public void writeBinaryData(final EdmEntitySet entitySet, final Object mediaEntityInstance,
      final BinaryData binaryData)
      throws ODataNotImplementedException, ODataNotFoundException, EdmException, ODataApplicationException {

    try {
      DataStore<Object> dataStore = getDataStore(entitySet);
      Object readEntry = dataStore.read(mediaEntityInstance);
      if (readEntry == null) {
        throw new ODataNotFoundException(ODataNotFoundException.ENTITY);
      } else {
        ANNOTATION_HELPER.setValueForAnnotatedField(
            mediaEntityInstance, EdmMediaResourceContent.class, binaryData.getData());
        ANNOTATION_HELPER.setValueForAnnotatedField(
            mediaEntityInstance, EdmMediaResourceMimeType.class, binaryData.getMimeType());
      }
    } catch (AnnotationHelper.ODataAnnotationException e) {
      throw new AnnotationRuntimeException("Invalid media resource annotation at entity set '" + entitySet.getName()
          + "' with message '" + e.getMessage() + "'.", e);
    }
  }

  /**
   * <p>Updates a single data object identified by the specified entity set and key fields of
   * the data object.</p>
   * @param entitySet the {@link EdmEntitySet} the object must correspond to
   * @param data the data object of the new entity
   * @return updated data object instance
   * @throws org.apache.olingo.odata2.api.exception.ODataNotImplementedException
   * @throws org.apache.olingo.odata2.api.edm.EdmException
   * @throws org.apache.olingo.odata2.api.exception.ODataApplicationException
   */
  public Object updateData(final EdmEntitySet entitySet, final Object data)
      throws EdmException, ODataApplicationException {

    DataStore<Object> dataStore = getDataStore(entitySet);
    return dataStore.update(data);
  }

  @Override
  public void deleteData(final EdmEntitySet entitySet, final Map<String, Object> keys)
      throws ODataNotImplementedException, ODataNotFoundException, EdmException, ODataApplicationException {
    DataStore<Object> dataStore = getDataStore(entitySet);
    Object keyInstance = dataStore.createInstance();
    ANNOTATION_HELPER.setKeyFields(keyInstance, keys);
    dataStore.delete(keyInstance);
  }

  @Override
  public Object createData(final EdmEntitySet entitySet, final Object data)
      throws ODataNotImplementedException, EdmException, ODataApplicationException {

    DataStore<Object> dataStore = getDataStore(entitySet);
    return dataStore.create(data);
  }

  @Override
  public void deleteRelation(final EdmEntitySet sourceEntitySet, final Object sourceData,
      final EdmEntitySet targetEntitySet,
      final Map<String, Object> targetKeys)
      throws ODataNotImplementedException, ODataNotFoundException, EdmException, ODataApplicationException {
    throw new ODataNotImplementedException(ODataNotImplementedException.COMMON);
  }

  @Override
  public void writeRelation(final EdmEntitySet sourceEntitySet, final Object sourceEntity,
      final EdmEntitySet targetEntitySet,
      final Map<String, Object> targetEntityValues)
      throws ODataNotImplementedException, ODataNotFoundException, EdmException, ODataApplicationException {
    // get common data
    DataStore<Object> sourceStore = dataStoreManager.getDataStore(sourceEntitySet.getName());
    DataStore<Object> targetStore = dataStoreManager.getDataStore(targetEntitySet.getName());

    AnnotationHelper.AnnotatedNavInfo commonNavInfo = ANNOTATION_HELPER.getCommonNavigationInfo(
        sourceStore.getDataTypeClass(), targetStore.getDataTypeClass());

    // get and validate source fields
    Field sourceField = commonNavInfo.getFromField();
    if (sourceField == null) {
      throw new AnnotationRuntimeException("Missing source field for related data (sourceStore='" + sourceStore
          + "', targetStore='" + targetStore + "').");
    }

    // get related target entity
    Object targetEntity = targetStore.createInstance();
    ANNOTATION_HELPER.setKeyFields(targetEntity, targetEntityValues);
    targetEntity = targetStore.read(targetEntity);

    // set at source
    setValueAtNavigationField(sourceEntity, sourceField, targetEntity);
    // set at target
    Field targetField = commonNavInfo.getToField();
    if (targetField != null) {
      setValueAtNavigationField(targetEntity, targetField, sourceEntity);
    }
  }

  /**
   * Set (Multiplicity != *) or add (Multiplicity == *) <code>value</code> at <code>field</code>
   * of <code>instance</code>.
   * 
   * @param instance
   * @param field
   * @param value
   * @throws EdmException
   */
  private void setValueAtNavigationField(final Object instance, final Field field, final Object value) {
    Class<?> fieldTypeClass = field.getType();
    if (Collection.class.isAssignableFrom(fieldTypeClass)) {
      @SuppressWarnings("unchecked")
      Collection<Object> collection = (Collection<Object>) ANNOTATION_HELPER.getValueForField(
          instance, field.getName(), EdmNavigationProperty.class);
      if (collection == null) {
        collection = new ArrayList<>();
        setValue(instance, field, collection);
      }
      collection.add(value);
    } else if (fieldTypeClass.isArray()) {
      throw new AnnotationRuntimeException("Write relations for internal used arrays is not supported.");
    } else {
      setValue(instance, field, value);
    }
  }

  /**
   * Returns corresponding DataStore for EdmEntitySet or if no data store is registered an
   * AnnotationRuntimeException is thrown.
   * Never returns NULL.
   * 
   * @param entitySet for which the corresponding DataStore is returned
   * @return a DataStore object
   * @throws EdmException
   * @throws AnnotationRuntimeException if no DataStore is found
   */
  private DataStore<Object> getDataStore(final EdmEntitySet entitySet) throws EdmException {
    final String name = entitySet.getName();
    DataStore<Object> dataStore = dataStoreManager.getDataStore(name);
    if (dataStore == null) {
      throw new AnnotationRuntimeException("No DataStore found for entity set '" + entitySet + "'.");
    }
    return dataStore;
  }

  private Object getValue(final Field field, final Object instance) {
    try {
      boolean access = field.isAccessible();
      field.setAccessible(true);
      Object value = field.get(instance);
      field.setAccessible(access);
      return value;
    } catch (IllegalArgumentException | IllegalAccessException e) {
      throw new AnnotationRuntimeException("Error for getting value of field '"
          + field + "' at instance '" + instance + "'.", e);
    }
  }

  private void setValue(final Object instance, final Field field, final Object value) {
    try {
      boolean access = field.isAccessible();
      field.setAccessible(true);
      field.set(instance, value);
      field.setAccessible(access);
    } catch (IllegalArgumentException | IllegalAccessException e) {
      throw new AnnotationRuntimeException("Error for setting value of field: '"
          + field + "' at instance: '" + instance + "'.", e);
    }
  }
} @EdmEntityType(name = "Building", namespace = ModelSharedConstants.NAMESPACE_1)
@EdmEntitySet(name = "Buildings")
public class Building {
  @EdmKey
  @EdmProperty(type = EdmType.INT32, facets = @EdmFacets(nullable = false))
  private String id;
  @EdmProperty
  private String name;
  @EdmProperty(name = "Image", type = EdmType.BINARY)
  private byte[] image;
  @EdmNavigationProperty(name = "nb_Rooms", toType = Room.class,
      association = "BuildingRooms", toMultiplicity = Multiplicity.MANY)
  private List<Room> rooms = new ArrayList<>();

  public Building() {}

  public String getId() {
    return id;
  }

  public void setId(final String id) {
    this.id = id;
  }

  public void setName(final String name) {
    this.name = name;
  }

  public String getName() {
    return name;
  }

  public void setImage(final byte[] byteArray) {
    image = byteArray;
  }

  public byte[] getImage() {
    if (image == null) {
      return null;
    } else {
      return image.clone();
    }
  }

  public List<Room> getRooms() {
    return rooms;
  }

  @Override
  public int hashCode() {
    return id == null ? 0 : id.hashCode();
  }

  @Override
  public boolean equals(final Object obj) {
    return this == obj
        || obj != null && getClass() == obj.getClass() && id == ((Building) obj).id;
  }

  @Override
  public String toString() {
    return "{\"Id\":\"" + id + "\",\"Name\":\"" + name + "\",\"Image\":\"" + Arrays.toString(image) + "\"}";
  }
} @EdmEntityType(name = "Room", namespace = ModelSharedConstants.NAMESPACE_1)
@EdmEntitySet(name = "Rooms")
public class Room extends RefBase {

  @EdmProperty
  private Integer seats;
  @EdmProperty(facets = @EdmFacets(scale = 0, precision = 0))
  @EdmConcurrencyControl
  private Integer version;
  @EdmNavigationProperty(name = "nr_Building", association = "BuildingRooms")
  private Building building;
  @EdmNavigationProperty(name = "nr_Employees")
  private List<Employee> employees = new ArrayList<>();

  public Room() {
    this(0, null);
  }

  public Room(final int id, final String name) {
    super(id, name);
  }

  public void setSeats(final int seats) {
    this.seats = seats;
  }

  public int getSeats() {
    return seats;
  }

  public void setVersion(final int version) {
    this.version = version;
  }

  public int getVersion() {
    return version;
  }

  public void setBuilding(final Building building) {
    this.building = building;
  }

  public Building getBuilding() {
    return building;
  }

  public List<Employee> getEmployees() {
    return employees;
  }

  @Override
  public int hashCode() {
    return id;
  }

  @Override
  public boolean equals(final Object obj) {
    return this == obj
        || obj != null && getClass() == obj.getClass() && id == ((Room) obj).id;
  }

  @Override
  public String toString() {
    return "{\"Id\":\"" + id + "\",\"Name\":\"" + name + "\",\"Seats\":" + seats + ",\"Version\":" + version + "}";
  }
} public class JsonHelper {

  public static LinkedTreeMap<?, ?> getLinkedTreeMap(final String body) {
    Gson gson = new Gson();
    final LinkedTreeMap<?, ?> map = gson.fromJson(body, new TypeToken<LinkedTreeMap<?, ?>>() {}.getType());
    if (map.get("d") instanceof LinkedTreeMap<?, ?>) {
      return (LinkedTreeMap<?, ?>) map.get("d");
    } else {
      return map;
    }
  }

  public static List<LinkedTreeMap<?, ?>> getResults(final String body) {
    Gson gson = new Gson();
    final LinkedTreeMap<?, ?> map = gson.fromJson(body, new TypeToken<LinkedTreeMap<?, ?>>() {}.getType());
    if (map.get("d") instanceof LinkedTreeMap<?, ?>) {
      LinkedTreeMap dMap = (LinkedTreeMap<?, ?>) map.get("d");
      if(dMap.get("results") instanceof List) {
        return (List) dMap.get("results");
      }
    }
    throw new RuntimeException("Expected results json array was not found for JSON body:\n" + body);
  }
} public class StringHelper {

  public static class Stream {
    private final byte[] data;

    private Stream(final byte[] data) {
      this.data = data;
    }

    public Stream(final String content, final String charset) throws UnsupportedEncodingException {
      this(content.getBytes(charset));
    }

    public InputStream asStream() {
      return new ByteArrayInputStream(data);
    }

    public byte[] asArray() {
      return data;
    }

    public String asString() {
      return asString("UTF-8");
    }

    public String asString(final String charsetName) {
      return new String(data, Charset.forName(charsetName));
    }

    public Stream print(final OutputStream out) throws IOException {
      out.write(data);
      return this;
    }

    public Stream print() throws IOException {
      return print(System.out);
    }

    public String asStringWithLineSeparation(String separator) throws IOException {
      BufferedReader br = new BufferedReader(new StringReader(asString()));
      StringBuilder sb = new StringBuilder(br.readLine());
      String line = br.readLine();
      while (line != null) {
        sb.append(separator).append(line);
        line = br.readLine();
      }
      return sb.toString();
    }

    public InputStream asStreamWithLineSeparation(String separator) throws IOException {
      String asString = asStringWithLineSeparation(separator);
      return new ByteArrayInputStream(asString.getBytes("UTF-8"));
    }

    /**
     * Number of lines separated by line breaks (<code>CRLF</code>).
     * A content string like <code>text\r\nmoreText</code> will result in
     * a line count of <code>2</code>.
     * 
     * @return lines count
     */
    public int linesCount() {
      return StringHelper.countLines(asString(), "\r\n");
    }
  }

  public static Stream toStream(final InputStream stream) throws IOException {
    byte[] result = new byte[0];
    byte[] tmp = new byte[8192];
    int readCount = stream.read(tmp);
    while (readCount >= 0) {
      byte[] innerTmp = new byte[result.length + readCount];
      System.arraycopy(result, 0, innerTmp, 0, result.length);
      System.arraycopy(tmp, 0, innerTmp, result.length, readCount);
      result = innerTmp;
      readCount = stream.read(tmp);
    }
    stream.close();
    return new Stream(result);
  }

  public static Stream toStream(final String content) {
    try {
      return new Stream(content, "UTF-8");
    } catch (UnsupportedEncodingException e) {
      throw new RuntimeException("UTF-8 should be supported on each system.");
    }
  }

  public static String inputStreamToString(final InputStream in, final boolean preserveLineBreaks) throws IOException {
    final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(in, Charset.forName("UTF-8")));
    final StringBuilder stringBuilder = new StringBuilder();
    String line = null;

    while ((line = bufferedReader.readLine()) != null) {
      stringBuilder.append(line);
      if (preserveLineBreaks) {
        stringBuilder.append("\n");
      }
    }

    bufferedReader.close();

    return stringBuilder.toString();
  }

  public static String inputStreamToStringCRLFLineBreaks(final InputStream in) throws IOException {
    final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(in, Charset.forName("UTF-8")));
    final StringBuilder stringBuilder = new StringBuilder();
    String line = null;

    while ((line = bufferedReader.readLine()) != null) {
      stringBuilder.append(line);
      stringBuilder.append("\r\n");
    }

    bufferedReader.close();

    return stringBuilder.toString();
  }

  public static int countLines(final String content) {
    return countLines(content, "\r\n");
  }

  public static int countLines(final String content, final String lineBreak) {
    if (content == null) {
      return -1;
    }

    int lastPos = content.indexOf(lineBreak);
    int count = 1;

    while (lastPos >= 0) {
      lastPos = content.indexOf(lineBreak, lastPos + 1);
      count++;
    }
    return count;
  }

  public static String inputStreamToString(final InputStream in) throws IOException {
    return inputStreamToString(in, false);
  }

  public static String httpEntityToString(final HttpEntity entity) throws IOException, IllegalStateException {
    return inputStreamToString(entity.getContent());
  }

  /**
   * Encapsulate given content in an {@link InputStream} with charset <code>UTF-8</code>.
   * 
   * @param content to encapsulate content
   * @return content as stream
   */
  public static InputStream encapsulate(final String content) {
    try {
      return encapsulate(content, "UTF-8");
    } catch (UnsupportedEncodingException e) {
      // we know that UTF-8 is supported
      throw new TestUtilRuntimeException("UTF-8 MUST be supported.", e);
    }
  }

  /**
   * Encapsulate given content in an {@link InputStream} with given charset.
   * 
   * @param content to encapsulate content
   * @param charset to be used charset
   * @return content as stream
   * @throws UnsupportedEncodingException if charset is not supported
   */
  public static InputStream encapsulate(final String content, final String charset)
      throws UnsupportedEncodingException {
    return new ByteArrayInputStream(content.getBytes(charset));
  }

  /**
   * Generate a string with given length containing random upper case characters ([A-Z]).
   * 
   * @param len length of to generated string
   * @return random upper case characters ([A-Z]).
   */
  public static InputStream generateDataStream(final int len) {
    return encapsulate(generateData(len));
  }

  /**
   * Generates a string with given length containing random upper case characters ([A-Z]).
   * @param len length of the generated string
   * @return random upper case characters ([A-Z])
   */
  public static String generateData(final int len) {
    Random random = new Random();
    StringBuilder b = new StringBuilder(len);
    for (int j = 0; j < len; j++) {
      final char c = (char) ('A' + random.nextInt('Z' - 'A' + 1));
      b.append(c);
    }
    return b.toString();
  }

} public class EdmMock {

  public static EdmEntitySet getEntitySet(Edm edm, String entitySetName) throws EdmException {
    List<EdmEntitySet> entitySets = edm.getEntitySets();
    for (EdmEntitySet entitySet : entitySets) {
      if(entitySet.getName().equals(entitySetName)) {
        return entitySet;
      }
    }
    return null;
  }

  public static Edm createMockEdm() throws ODataException {
    EdmEntityContainer defaultContainer = mock(EdmEntityContainer.class);
    when(defaultContainer.isDefaultEntityContainer()).thenReturn(true);

    final EdmEntitySet employeeEntitySet =
        createEntitySetMock(defaultContainer, "Employees", EdmSimpleTypeKind.String, "EmployeeId");
    final EdmEntitySet teamEntitySet = createEntitySetMock(defaultContainer, "Teams", EdmSimpleTypeKind.String, "Id");
    final EdmEntitySet roomEntitySet = createEntitySetMock(defaultContainer, "Rooms", EdmSimpleTypeKind.String, "Id");
    final EdmEntitySet managerEntitySet =
        createEntitySetMock(defaultContainer, "Managers", EdmSimpleTypeKind.String, "EmployeeId");
    final EdmEntitySet buildingEntitySet =
        createEntitySetMock(defaultContainer, "Buildings", EdmSimpleTypeKind.String, "Id");

    final List<EdmEntitySet> edmEntitySets = Arrays.asList(employeeEntitySet, teamEntitySet,
        roomEntitySet, managerEntitySet, buildingEntitySet);

    EdmEntityType employeeType = employeeEntitySet.getEntityType();
    when(employeeType.hasStream()).thenReturn(true);
    EdmMapping employeeTypeMapping = Mockito.mock(EdmMapping.class);
    when(employeeTypeMapping.getMediaResourceMimeTypeKey()).thenReturn("getImageType");
    when(employeeType.getMapping()).thenReturn(employeeTypeMapping);
    when(employeeType.getPropertyNames()).thenReturn(Arrays.asList(
        "EmployeeId", "EmployeeName", "ManagerId", "RoomId", "TeamId",
        "Location", "Age", "EntryDate", "ImageUrl"));
    when(employeeType.getNavigationPropertyNames()).thenReturn(Arrays.asList("ne_Manager", "ne_Team", "ne_Room"));

    EdmProperty employeeNameProperty = createProperty("EmployeeName", EdmSimpleTypeKind.String, employeeType);
    EdmCustomizableFeedMappings employeeTitleMappings = mock(EdmCustomizableFeedMappings.class);
    when(employeeTitleMappings.getFcTargetPath()).thenReturn(EdmTargetPath.SYNDICATION_TITLE);
    when(employeeTitleMappings.isFcKeepInContent()).thenReturn(true);
    when(employeeNameProperty.getCustomizableFeedMappings()).thenReturn(employeeTitleMappings);

    createProperty("ManagerId", EdmSimpleTypeKind.String, employeeType);
    createProperty("RoomId", EdmSimpleTypeKind.String, employeeType);
    createProperty("TeamId", EdmSimpleTypeKind.String, employeeType);
    createProperty("Age", EdmSimpleTypeKind.Int32, employeeType);

    EdmProperty employeeEntryDateProperty = createProperty("EntryDate", EdmSimpleTypeKind.DateTime, employeeType);
    EdmCustomizableFeedMappings employeeUpdatedMappings = mock(EdmCustomizableFeedMappings.class);
    when(employeeUpdatedMappings.getFcTargetPath()).thenReturn(EdmTargetPath.SYNDICATION_UPDATED);
    when(employeeUpdatedMappings.isFcKeepInContent()).thenReturn(true);
    when(employeeEntryDateProperty.getCustomizableFeedMappings()).thenReturn(employeeUpdatedMappings);
    EdmFacets employeeEntryDateFacets = mock(EdmFacets.class);
    when(employeeEntryDateFacets.getMaxLength()).thenReturn(null);
    when(employeeEntryDateFacets.isNullable()).thenReturn(Boolean.TRUE);
    when(employeeEntryDateProperty.getFacets()).thenReturn(employeeEntryDateFacets);

    createProperty("ImageUrl", EdmSimpleTypeKind.String, employeeType);

    final EdmComplexType locationComplexType = mock(EdmComplexType.class);
    when(locationComplexType.getKind()).thenReturn(EdmTypeKind.COMPLEX);
    when(locationComplexType.getName()).thenReturn("c_Location");
    when(locationComplexType.getNamespace()).thenReturn("RefScenario");
    when(locationComplexType.getPropertyNames()).thenReturn(Arrays.asList("City", "Country"));

    final EdmProperty locationComplexProperty = mock(EdmProperty.class);
    when(locationComplexProperty.getType()).thenReturn(locationComplexType);
    when(locationComplexProperty.getName()).thenReturn("Location");
    when(employeeType.getProperty("Location")).thenReturn(locationComplexProperty);
    createProperty("Country", EdmSimpleTypeKind.String, locationComplexType);

    final EdmComplexType cityComplexType = mock(EdmComplexType.class);
    when(cityComplexType.getKind()).thenReturn(EdmTypeKind.COMPLEX);
    when(cityComplexType.getName()).thenReturn("c_City");
    when(cityComplexType.getNamespace()).thenReturn("RefScenario");
    when(cityComplexType.getPropertyNames()).thenReturn(Arrays.asList("PostalCode", "CityName"));

    EdmProperty cityProperty = mock(EdmProperty.class);
    when(cityProperty.getType()).thenReturn(cityComplexType);
    when(cityProperty.getName()).thenReturn("City");
    when(locationComplexType.getProperty("City")).thenReturn(cityProperty);

    createProperty("PostalCode", EdmSimpleTypeKind.String, cityComplexType);
    createProperty("CityName", EdmSimpleTypeKind.String, cityComplexType);

    createNavigationProperty("ne_Manager", EdmMultiplicity.ONE, employeeEntitySet, managerEntitySet);
    createNavigationProperty("ne_Team", EdmMultiplicity.ONE, employeeEntitySet, teamEntitySet);
    createNavigationProperty("ne_Room", EdmMultiplicity.ONE, employeeEntitySet, roomEntitySet);

    EdmEntityType teamType = teamEntitySet.getEntityType();
    when(teamType.getPropertyNames()).thenReturn(Arrays.asList("Id", "Name", "isScrumTeam"));
    createProperty("Name", EdmSimpleTypeKind.String, teamType);
    createProperty("isScrumTeam", EdmSimpleTypeKind.Boolean, teamType);
    when(teamType.getNavigationPropertyNames()).thenReturn(Arrays.asList("nt_Employees"));
    createNavigationProperty("nt_Employees", EdmMultiplicity.MANY, teamEntitySet, employeeEntitySet);

    EdmEntityType roomType = roomEntitySet.getEntityType();
    when(roomType.getPropertyNames()).thenReturn(Arrays.asList("Id", "Name", "Seats", "Version"));
    EdmProperty roomId = roomEntitySet.getEntityType().getKeyProperties().get(0);
    EdmFacets roomIdFacets = mock(EdmFacets.class);
    when(roomIdFacets.getMaxLength()).thenReturn(100);
    when(roomId.getFacets()).thenReturn(roomIdFacets);
    createProperty("Name", EdmSimpleTypeKind.String, roomType);
    createProperty("Seats", EdmSimpleTypeKind.Int16, roomType);
    EdmProperty roomVersion = createProperty("Version", EdmSimpleTypeKind.Int16, roomType);
    EdmFacets roomVersionFacets = mock(EdmFacets.class);
    when(roomVersionFacets.getConcurrencyMode()).thenReturn(EdmConcurrencyMode.Fixed);
    when(roomVersionFacets.isNullable()).thenReturn(null);
    when(roomVersion.getFacets()).thenReturn(roomVersionFacets);
    when(roomType.getNavigationPropertyNames()).thenReturn(Arrays.asList("nr_Employees", "nr_Building"));
    createNavigationProperty("nr_Employees", EdmMultiplicity.MANY, roomEntitySet, employeeEntitySet);
    createNavigationProperty("nr_Building", EdmMultiplicity.ONE, roomEntitySet, buildingEntitySet);

    createNavigationProperty("ne_Manager", EdmMultiplicity.ONE, managerEntitySet, managerEntitySet);
    createNavigationProperty("ne_Team", EdmMultiplicity.ONE, managerEntitySet, teamEntitySet);
    createNavigationProperty("ne_Room", EdmMultiplicity.ONE, managerEntitySet, roomEntitySet);
    createNavigationProperty("nm_Employees", EdmMultiplicity.MANY, managerEntitySet, employeeEntitySet);

    EdmEntityType buildingType = buildingEntitySet.getEntityType();
    when(buildingType.getPropertyNames()).thenReturn(Arrays.asList("Id", "Name", "Image"));
    createProperty("Name", EdmSimpleTypeKind.String, buildingType);
    createProperty("Image", EdmSimpleTypeKind.Binary, buildingType);
    when(buildingType.getNavigationPropertyNames()).thenReturn(Arrays.asList("nb_Rooms"));
    createNavigationProperty("nb_Rooms", EdmMultiplicity.MANY, buildingEntitySet, roomEntitySet);

    EdmFunctionImport employeeSearchFunctionImport =
        createFunctionImportMock(defaultContainer, "EmployeeSearch", employeeType, EdmMultiplicity.MANY);
    when(employeeSearchFunctionImport.getEntitySet()).thenReturn(employeeEntitySet);
    EdmParameter employeeSearchParameter = mock(EdmParameter.class);
    when(employeeSearchParameter.getType()).thenReturn(EdmSimpleTypeKind.String.getEdmSimpleTypeInstance());
    when(employeeSearchFunctionImport.getParameterNames()).thenReturn(Arrays.asList("q"));
    when(employeeSearchFunctionImport.getParameter("q")).thenReturn(employeeSearchParameter);
    createFunctionImportMock(defaultContainer, "AllLocations", locationComplexType, EdmMultiplicity.MANY);
    createFunctionImportMock(defaultContainer, "AllUsedRoomIds", EdmSimpleTypeKind.String.getEdmSimpleTypeInstance(),
        EdmMultiplicity.MANY);
    createFunctionImportMock(defaultContainer, "MaximalAge", EdmSimpleTypeKind.Int16.getEdmSimpleTypeInstance(),
        EdmMultiplicity.ONE);
    createFunctionImportMock(defaultContainer, "MostCommonLocation", locationComplexType, EdmMultiplicity.ONE);
    EdmFunctionImport managerPhotoFunctionImport =
        createFunctionImportMock(defaultContainer, "ManagerPhoto", EdmSimpleTypeKind.Binary.getEdmSimpleTypeInstance(),
            EdmMultiplicity.ONE);
    EdmParameter managerPhotoParameter = mock(EdmParameter.class);
    when(managerPhotoParameter.getType()).thenReturn(EdmSimpleTypeKind.String.getEdmSimpleTypeInstance());
    EdmFacets managerPhotoParameterFacets = mock(EdmFacets.class);
    when(managerPhotoParameterFacets.isNullable()).thenReturn(false);
    when(managerPhotoParameter.getFacets()).thenReturn(managerPhotoParameterFacets);
    when(managerPhotoFunctionImport.getParameterNames()).thenReturn(Arrays.asList("Id"));
    when(managerPhotoFunctionImport.getParameter("Id")).thenReturn(managerPhotoParameter);
    EdmFunctionImport oldestEmployeeFunctionImport =
        createFunctionImportMock(defaultContainer, "OldestEmployee", employeeType, EdmMultiplicity.ONE);
    when(oldestEmployeeFunctionImport.getEntitySet()).thenReturn(employeeEntitySet);

    // Issue with not explicitly nullable parameters and facets
    EdmFunctionImport functionImportNullableParameter =
        createFunctionImportMock(defaultContainer, "FINullableParameter", EdmSimpleTypeKind.Boolean
            .getEdmSimpleTypeInstance(), EdmMultiplicity.ONE);
    EdmParameter nullableFIParameter = mock(EdmParameter.class);
    when(nullableFIParameter.getType()).thenReturn(EdmSimpleTypeKind.String.getEdmSimpleTypeInstance());
    EdmFacets nullableFIParameterFacets = mock(EdmFacets.class);
    when(nullableFIParameterFacets.isNullable()).thenReturn(null);
    when(nullableFIParameterFacets.getMaxLength()).thenReturn(new Integer(1));
    when(nullableFIParameter.getFacets()).thenReturn(nullableFIParameterFacets);
    when(functionImportNullableParameter.getParameterNames()).thenReturn(Arrays.asList("Id"));
    when(functionImportNullableParameter.getParameter("Id")).thenReturn(nullableFIParameter);

    EdmEntityContainer specificContainer = mock(EdmEntityContainer.class);
    when(specificContainer.getEntitySet("Employees")).thenReturn(employeeEntitySet);
    when(specificContainer.getName()).thenReturn("Container1");

    EdmEntityType photoEntityType = mock(EdmEntityType.class);
    when(photoEntityType.getName()).thenReturn("Photo");
    when(photoEntityType.getNamespace()).thenReturn("RefScenario2");
    when(photoEntityType.getPropertyNames()).thenReturn(Arrays.asList(
        "Id", "Name", "Type", "Image", "BinaryData", "Содержание", "CustomProperty"));
    when(photoEntityType.getKeyPropertyNames()).thenReturn(Arrays.asList("Id", "Type"));
    when(photoEntityType.hasStream()).thenReturn(true);
    EdmMapping photoEntityTypeMapping = Mockito.mock(EdmMapping.class);
    when(photoEntityTypeMapping.getMediaResourceMimeTypeKey()).thenReturn("getType");
    when(photoEntityType.getMapping()).thenReturn(photoEntityTypeMapping);
    EdmProperty photoIdProperty = createProperty("Id", EdmSimpleTypeKind.Int32, photoEntityType);
    EdmFacets photoIdFacet = mock(EdmFacets.class);
    when(photoIdFacet.getConcurrencyMode()).thenReturn(EdmConcurrencyMode.Fixed);
    when(photoIdProperty.getFacets()).thenReturn(photoIdFacet);

    createProperty("Name", EdmSimpleTypeKind.String, photoEntityType);
    final EdmProperty photoTypeProperty = createProperty("Type", EdmSimpleTypeKind.String, photoEntityType);
    when(photoEntityType.getKeyProperties()).thenReturn(Arrays.asList(photoIdProperty, photoTypeProperty));

    EdmProperty photoImageProperty = createProperty("Image", EdmSimpleTypeKind.Binary, photoEntityType);
    EdmMapping imageMapping = mock(EdmMapping.class);
    when(imageMapping.getMediaResourceMimeTypeKey()).thenReturn("getImageType");
    when(photoImageProperty.getMapping()).thenReturn(imageMapping);

    EdmProperty binaryDataProperty = createProperty("BinaryData", EdmSimpleTypeKind.Binary, photoEntityType);
    when(binaryDataProperty.getMimeType()).thenReturn("image/jpeg");

    EdmProperty photoRussianProperty = createProperty("Содержание", EdmSimpleTypeKind.String, photoEntityType);
    EdmFacets photoRussianFacets = mock(EdmFacets.class);
    when(photoRussianFacets.isNullable()).thenReturn(true);
    when(photoRussianFacets.isUnicode()).thenReturn(true);
    when(photoRussianFacets.getMaxLength()).thenReturn(Integer.MAX_VALUE);
    when(photoRussianProperty.getFacets()).thenReturn(photoRussianFacets);
    CustomizableFeedMappings photoRussianMapping = mock(CustomizableFeedMappings.class);
    when(photoRussianMapping.getFcKeepInContent()).thenReturn(false);
    when(photoRussianMapping.getFcNsPrefix()).thenReturn("ру");
    when(photoRussianMapping.getFcNsUri()).thenReturn("http://localhost");
    when(photoRussianMapping.getFcTargetPath()).thenReturn("Содержание");
    when(photoRussianProperty.getCustomizableFeedMappings()).thenReturn(photoRussianMapping);

    EdmProperty customProperty = createProperty("CustomProperty", EdmSimpleTypeKind.String, photoEntityType);
    CustomizableFeedMappings customFeedMapping = mock(CustomizableFeedMappings.class);
    when(customFeedMapping.getFcKeepInContent()).thenReturn(false);
    when(customFeedMapping.getFcNsPrefix()).thenReturn("custom");
    when(customFeedMapping.getFcNsUri()).thenReturn("http://localhost");
    when(customFeedMapping.getFcTargetPath()).thenReturn("TarPath");
    when(customProperty.getCustomizableFeedMappings()).thenReturn(customFeedMapping);

    EdmEntitySet photoEntitySet = mock(EdmEntitySet.class);
    when(photoEntitySet.getName()).thenReturn("Photos");
    when(photoEntitySet.getEntityType()).thenReturn(photoEntityType);
    EdmEntityContainer photoContainer = mock(EdmEntityContainer.class);
    when(photoContainer.isDefaultEntityContainer()).thenReturn(false);
    when(photoContainer.getEntitySet("Photos")).thenReturn(photoEntitySet);
    when(photoContainer.getName()).thenReturn("Container2");

    when(photoEntitySet.getEntityContainer()).thenReturn(photoContainer);

    Edm edm = mock(Edm.class);
    EdmServiceMetadata serviceMetadata = mock(EdmServiceMetadata.class);
    when(serviceMetadata.getDataServiceVersion()).thenReturn("MockEdm");
    when(edm.getServiceMetadata()).thenReturn(serviceMetadata);
    when(edm.getDefaultEntityContainer()).thenReturn(defaultContainer);
    when(edm.getEntityContainer("Container1")).thenReturn(specificContainer);
    when(edm.getEntityContainer("Container2")).thenReturn(photoContainer);
    when(edm.getEntityType("RefScenario", "Employee")).thenReturn(employeeType);
    when(edm.getEntityType("RefScenario", "Team")).thenReturn(teamType);
    when(edm.getEntityType("RefScenario", "Room")).thenReturn(roomType);
    when(edm.getEntityType("RefScenario", "Building")).thenReturn(buildingType);
    when(edm.getComplexType("RefScenario", "c_Location")).thenReturn(locationComplexType);
    when(edm.getEntityType("RefScenario2", "Photo")).thenReturn(photoEntityType);
    when(edm.getEntitySets()).thenReturn(edmEntitySets);

    return edm;
  }

  private static EdmNavigationProperty createNavigationProperty(final String name, final EdmMultiplicity multiplicity,
      final EdmEntitySet entitySet, final EdmEntitySet targetEntitySet) throws EdmException {
    EdmType navigationType = mock(EdmType.class);
    when(navigationType.getKind()).thenReturn(EdmTypeKind.ENTITY);

    EdmNavigationProperty navigationProperty = mock(EdmNavigationProperty.class);
    when(navigationProperty.getName()).thenReturn(name);
    when(navigationProperty.getType()).thenReturn(navigationType);
    when(navigationProperty.getMultiplicity()).thenReturn(multiplicity);

    when(entitySet.getEntityType().getProperty(name)).thenReturn(navigationProperty);
    when(entitySet.getRelatedEntitySet(navigationProperty)).thenReturn(targetEntitySet);

    return navigationProperty;
  }

  private static EdmProperty createProperty(final String name, final EdmSimpleTypeKind kind,
      final EdmStructuralType entityType) throws EdmException {
    EdmProperty property = mock(EdmProperty.class);
    when(property.getType()).thenReturn(kind.getEdmSimpleTypeInstance());
    when(property.getName()).thenReturn(name);
    when(property.isSimple()).thenReturn(true);
    when(entityType.getProperty(name)).thenReturn(property);
    return property;
  }

  private static EdmEntitySet createEntitySetMock(final EdmEntityContainer container, final String name,
      final EdmSimpleTypeKind kind, final String keyPropertyId) throws EdmException {
    final EdmEntityType entityType = createEntityTypeMock(name.substring(0, name.length() - 1), kind, keyPropertyId);

    EdmEntitySet entitySet = mock(EdmEntitySet.class);
    when(entitySet.getName()).thenReturn(name);
    when(entitySet.getEntityType()).thenReturn(entityType);

    when(entitySet.getEntityContainer()).thenReturn(container);

    when(container.getEntitySet(name)).thenReturn(entitySet);

    return entitySet;
  }

  private static EdmEntityType createEntityTypeMock(final String name, final EdmSimpleTypeKind kind,
      final String keyPropertyId) throws EdmException {
    EdmEntityType entityType = mock(EdmEntityType.class);
    when(entityType.getName()).thenReturn(name);
    when(entityType.getNamespace()).thenReturn("RefScenario");

    final EdmProperty keyProperty = createProperty(keyPropertyId, kind, entityType);
    EdmFacets facets = mock(EdmFacets.class);
    when(facets.getMaxLength()).thenReturn(null);
    when(facets.isNullable()).thenReturn(false);
    when(keyProperty.getFacets()).thenReturn(facets);

    when(entityType.getKind()).thenReturn(EdmTypeKind.ENTITY);
    when(entityType.getPropertyNames()).thenReturn(Arrays.asList(keyPropertyId));
    when(entityType.getKeyPropertyNames()).thenReturn(Arrays.asList(keyPropertyId));
    when(entityType.getKeyProperties()).thenReturn(Arrays.asList(keyProperty));

    return entityType;
  }

  private static EdmFunctionImport createFunctionImportMock(final EdmEntityContainer container, final String name,
      final EdmType type, final EdmMultiplicity multiplicity) throws EdmException {
    EdmTyped returnType = mock(EdmTyped.class);
    when(returnType.getType()).thenReturn(type);
    when(returnType.getMultiplicity()).thenReturn(multiplicity);

    EdmFunctionImport functionImport = mock(EdmFunctionImport.class);
    when(functionImport.getName()).thenReturn(name);
    when(functionImport.getReturnType()).thenReturn(returnType);
    when(functionImport.getHttpMethod()).thenReturn(ODataHttpMethod.GET.name());

    when(container.getFunctionImport(name)).thenReturn(functionImport);

    return functionImport;
  }
}
import com.google.gson.internal.LinkedTreeMap;
import org.apache.olingo.odata2.api.edm.Edm;
import org.apache.olingo.odata2.api.edm.EdmEntitySet;
import org.apache.olingo.odata2.api.exception.ODataException;
import org.apache.olingo.odata2.api.processor.ODataContext;
import org.apache.olingo.odata2.api.processor.ODataResponse;
import org.apache.olingo.odata2.api.uri.PathInfo;
import org.apache.olingo.odata2.api.uri.UriInfo;
import org.apache.olingo.odata2.api.uri.expression.FilterExpression;
import org.apache.olingo.odata2.api.uri.expression.OrderByExpression;
import org.apache.olingo.odata2.api.uri.info.GetEntitySetUriInfo;
import org.apache.olingo.odata2.core.uri.UriParserImpl;
import org.apache.olingo.odata2.janos.processor.api.data.ReadOptions;
import org.apache.olingo.odata2.janos.processor.api.data.ReadResult;
import org.apache.olingo.odata2.janos.processor.api.data.access.ValueAccess;
import org.apache.olingo.odata2.janos.processor.api.data.source.DataSource;
import org.apache.olingo.odata2.janos.processor.api.data.store.DataStoreManager;
import org.apache.olingo.odata2.janos.processor.core.data.access.AnnotationValueAccess;
import org.apache.olingo.odata2.janos.processor.core.data.access.BeanPropertyAccess;
import org.apache.olingo.odata2.janos.processor.core.data.source.AnnotationDataSource;
import org.apache.olingo.odata2.janos.processor.core.model.Building;
import org.apache.olingo.odata2.janos.processor.core.model.Room;
import org.apache.olingo.odata2.testutil.helper.JsonHelper;
import org.apache.olingo.odata2.testutil.helper.StringHelper;
import org.apache.olingo.odata2.testutil.mock.EdmMock;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mockito;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/*
 * 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.janos.processor.core;



/**
 *
 */
public class DataSourceProcessorTest {

  private DataSourceProcessor dataSourceProcessor;
  private DataSource mockedDataSource = Mockito.mock(DataSource.class);
  private ValueAccess mockedValueAccess = new BeanPropertyAccess();//Mockito.mock(ValueAccess.class);
  private DataStoreManager mockedDataStoreManager = Mockito.mock(DataStoreManager.class);