General idea

To fill the gap between a EDM definition with the API with full implementation of ODataSingleProcessor and the use of a generated EDM with the JPA Processor and JPA Annotations we created a PoC with Java Annotation to do a EDM definition with a generic AnnotationProcessor and to provide an option for simplified data access (read/write).

The idea behind this feature was to create your model as POJOs which then can be annotated with special @EdmXXX (Java) annotations to define the EDM for an OData Service. Based on these annotation then the edmx document ($metadata) can be generated as well as a generic Processor (ODataSingleProcessor) for e.g. JSON can be written. This feature will fill the gap between the currently existing JPA processor extension (which allows easy connection to a database with JPA annotations) and the need to do a full implementation of an Processor (ODataSingleProcessor).

As initial contribution and starting point for discussions about this feature on the issue OLINGO-32 was created and a feature branch with name PocEdmAnnotationsExtension (link and commit id: ae0d7d14161d4da7ff05492566bec7e66425c168) was created with a basic Proof of Concept.

Concept

Following submodules were created within the Apache Olingo project for realization of this PoC:

  • annotation-processor-api: API/Interfaces
  • annotation-processor-core: Implementation
  • annotation-processor-ref: Reference Scenario/Integration Tests
  • annotation-processor-ref-web: Reference Scenario as Web Projekt (Deployable WAR Archive)

Annotations

New defined (Java) annotations for definition of an EDM. These can be found in the Apache Olingo project within sub module olingo-odata2-api-annotation in package org.apache.olingo.odata2.api.annotation.edm and below. In addition to this list all available annotations can be found in the [Javadoc of odata2-api-annotation|^api-annotation-apidocs.zip].

  • EDM Annotations: All (currently) existing annotations for definition of the EDM.
    • EdmEntitySet: for definition of a entity set class
      • String name() default "": name for entity set
      • String container() default "": container name for this entity set
    • EdmEntityType: for definition of a entity type class
      • String name() default "": optional name for entity type. If not set class name is used.
      • String namespace() default "": namespace for this entity
    • EdmComplexType: for definition of a complex entity class which can be used as complex property.
      • String name() default "": optional name for entity type. If not set class name is used.
      • String namespace() default "":
    • EdmKey: marker annotation for property which is used as KeyProperty
    • EdmProperty: for definition of a property within an entity class
      • String name() default "":
      • EdmType type() default EdmType.Null: type of the property. If not set the type is guessed based on field type.
      • EdmFacets facets() default @EdmFacets:
    • EdmNavigationProperty:
      • String name() default "": used property name for Navigation property
      • String association() default "": used association name for Navigation property
      • String toRole() default "": target role name of this navigation
      • String toType() default Object.class;: class which provides an entity type (name) as target of this navigation
      • Multiplicity toMultiplicity() default Multiplicity.ONE;: multiplicity to target of this navigation
    • EdmType: The EdmTypes which can be used for property definition in the EDM. The available values are based on EdmSimpleTypeKind values as defined in OData with the additional type COMPLEX which can be used to explicit define a EdmProperty as complex.
    • EdmMediaResourceContent: Annotation for definition of an EdmProperty as media resource content for the according EntityType which contains the EdmProperty. Additionally an EdmEntityType will be flagged in the EDM as hasStream == true if an EdmProperty in conjunction with the EdmMediaResourceContent annotation is defined.
    • EdmMediaResourceMimeType: Annotation for definition of an EdmProperty as mime type for the media resource of the EdmEntityType which contains the EdmProperty. The value of the EdmMediaResourceMimeType annotated field will be used as Content-Type of the media content response (of an OData $value request).
    • EdmMediaResourceSource: Annotation for definition of an EdmProperty as media resource source for the EdmEntityType which contains the EdmProperty.
    • EdmFunctionImport: Annotation for definition of an method as an EdmFunctionImport call/endpoint
      • String name() default "";:
      • String entitySet() default "";:
      • ReturnType returnType();:
        • ReturnType
          • Type type();:
            • enum Type [SIMPLE, ENTITY, COMPLEX]

          • boolean isCollection() default false;: Define if the return type for the function import is a collection (entity set) or an single entity (entity).
      • HttpMethod httpMethod() default HttpMethod.GET;:
        • enum [HttpMethod] [OST, PUT, GET, MERGE, DELETE, PATCH]:

      • EdmDocumentation documentation() default @EdmDocumentation;:
    • EdmFunctionImportParameter:
    • EdmFacets: for definition of property facets
      • int maxLength() default -1;
      • int scale() default -1;
      • int precision() default -1;
      • boolean nullable() default false;
    • EdmConcurrencyControl: If a property is annotated with EdmConcurrencyControl this is equivalent with ConcurrencyMode = FIXED. Default of a property not annotated with EdmConcurrencyControl this is equivalent with ConcurrencyMode = NONE.
    • EdmDocumentation: for definition of additional documentation
      • String summary() default "";: Define a summary for this documentation.
      • String longDescription() default "";: Complete description for this documentation.

Class diagram for Data Access

For a simplified data access we provide a ListsProcessor (an extension of the ODataSingleProcessor) which uses a ListsDataSource for data persistence and ValueAccess for access of the properties of the data objects.

An architecture architecture overview is shown here: ArchitectureOverview.svg. An overview about involved classes is shown in this class diagram: ClassDiagram.svg.

In addition to the here shown Java code more documentation can be found in the Javadoc of "odata2-api-annotation": api-annotation-apidocs.zip and Javadoc of "odata2-annotation-processor-api": annotation-proc-api-apidocs.zip.

Entry points for Annotation Service

Via AnnotationServiceFactory

The common entry point for creation of an annotation based ODataService is via the AnnotationServiceFactory in org.apache.olingo.odata2.annotation.processor.api package in annotation-processor-api module. Therefore the AnnotationServiceFactory provides the methods ODataService createAnnotationService(String modelPackage) and ODataService createAnnotationService(Collection<Class<?>> annotatedClasses).

The createAnnotationService(String modelPackage) scan the given package for annotated classes (e.g. "org.apache.olingo.sample.model") and the createAnnotationService(Collection<Class<?>> annotatedClasses) scan classes in given collection for annotations.

For creation of the service instance the AnnotationServiceFactory create an instance of org.apache.olingo.odata2.annotation.processor.core.AnnotationServiceFactoryImpl (via the RuntimeDelegate pattern). A code snippet of the implementation can be found below in section ODataServiceFactory.

Interfaces and classes

DataSourceProcessor

/**
 * Abstract class for implementation of the centralized parts of OData processing,
 * allowing to use the simplified {@link DataSource} and {@link ValueAccess} for the
 * actual data handling.
 * <br/>
 * Extend this class and implement a DataSourceProcessor if the default implementation
 * (<code>ListProcessor</code> found in <code>annotation-processor-core module</code>) has to be overwritten.
 */
public abstract class DataSourceProcessor extends ODataSingleProcessor {

  protected final DataSource dataSource;
  protected final ValueAccess valueAccess;
  
  /**
   * Initialize a {@link DataSourceProcessor} in combination with given {@link DataSource} (providing data objects)
   * and {@link ValueAccess} (accessing values of data objects).
   * 
   * @param dataSource used for accessing the data objects
   * @param valueAccess for accessing the values provided by the data objects
   */
  public DataSourceProcessor(final DataSource dataSource, final ValueAccess valueAccess) {
    this.dataSource = dataSource;
    this.valueAccess = valueAccess;
  }
}

DataSource

public interface DataSource {

  /**
   * Retrieves the whole data list for the specified entity set.
   * @param entitySet the requested {@link EdmEntitySet}
   * @return the requested data list
   */
  List<?> readData(EdmEntitySet entitySet) throws ODataNotImplementedException, ODataNotFoundException, EdmException,
      ODataApplicationException;

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

  /**
   * <p>Retrieves data for the specified function import and key.</p>
   * <p>This method is called also for function imports that have defined in
   * their metadata an other HTTP method than <code>GET</code>.</p>
   * @param function the requested {@link EdmFunctionImport}
   * @param parameters the parameters of the function import
   * as map of parameter names to parameter values
   * @param keys the key of the returned entity set, as map of key names to key values,
   * if the return type of the function import is a collection of entities
   * (optional)
   * @return the requested data object, either a list or a single object;
   * if the function import's return type is of type <code>Binary</code>,
   * the returned object(s) must be of type {@link BinaryData}
   */
  Object readData(EdmFunctionImport function, Map<String, Object> parameters, 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 {@link EdmEntitySet} of the source entity
   * @param sourceData the data object of the source entity
   * @param targetEntitySet the requested target {@link EdmEntitySet}
   * @param targetKeys the key of the target entity as map of key names to key values
   * (optional)
   * @return the requested releated data object, either a list or a single object
   */
  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 {@link 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
   */
  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 {@link EdmEntitySet} the object must correspond to
   * @return the new data object
   */
  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 {@link EdmEntitySet} 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
   */
  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 {@link EdmEntitySet} of the entity to be deleted
   * @param keys the entity key as map of key names to key values
   */
  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 {@link EdmEntitySet} the object must correspond to
   * @param data the data object of the new entity
   */
  void 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 {@link EdmEntitySet} of the source entity
   * @param sourceData the data object of the source entity
   * @param targetEntitySet the {@link EdmEntitySet} of the target entity
   * @param targetKeys the key of the target entity as map of key names to key values
   * (optional)
   */
  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 {@link EdmEntitySet} of the source entity
   * @param sourceData the data object of the source entity
   * @param targetEntitySet the {@link EdmEntitySet} of the relation target
   * @param targetKeys the key of the target entity as map of key names to key values
   */
  void writeRelation(EdmEntitySet sourceEntitySet, Object sourceData, EdmEntitySet targetEntitySet,
      Map<String, Object> targetKeys) throws ODataNotImplementedException, ODataNotFoundException, EdmException,
      ODataApplicationException;

ValueAccess

/**
 * This interface is intended to access values in a Java object.
 */
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 {@link EdmProperty}
   * @return the requested property value
   */
  public <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 {@link EdmProperty}
   * @param value    the new value of the property
   */
  public <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 {@link EdmProperty}
   * @return the requested Java type
   */
  public <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 {@link EdmMapping}
   * @return the requested value
   */
  public <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 {@link EdmMapping}
   * @param value    the new value
   */
  public <T, V> void setMappingValue(T data, final EdmMapping mapping, final V value) throws ODataException;
}

POC realization

Edm Provider and Json Processor for annotated model

The generic Edm Provider and Json Processor for an annotated model can be found in the Apache Olingo project within sub module olingo-odata2-annotation-processor-core in package org.apache.olingo.odata2.annotation.processor.core and below.

For the Edm Provider the entry class is org.apache.olingo.odata2.annotation.processor.core.edm.AnnotationEdmProvider and for the Json Processor it is org.apache.olingo.odata2.core.annotation.processor.AnnotationProcessor.

Generic data access (via ListsProcessor, ValueAccess, GenericDs)

Code samples

Model POJOs

====== Base Entity: ======

/**
 *
 */
@EdmEntityType(name="Base")
public abstract class RefBase {
  @EdmProperty(name="Name")
  protected String name;
  @EdmKey
  @EdmProperty(name="Id", type = EdmType.STRING)
  protected int id;

  public String getName() {
    return name;
  }

  public String getId() {
    return Integer.toString(id);
  }

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

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

====== Team Entity: ======

@EdmEntityType(name = "Team")
@EdmEntitySet(name = "Teams")
public class Team extends RefBase {
  @EdmProperty(type = EdmType.BOOLEAN)
  private Boolean isScrumTeam;
  @EdmNavigationProperty(name = "nt_Employees", association = "TeamEmployees")
  private List<Employee> employees = new ArrayList<Employee>();

  public Boolean isScrumTeam() {
    return isScrumTeam;
  }

  public void setScrumTeam(final Boolean isScrumTeam) {
    this.isScrumTeam = isScrumTeam;
  }
  
  public void addEmployee(Employee e) {
    this.employees.add(e);
  }

  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 == ((Team) obj).id;
  }

  @Override
  public String toString() {
    return "{\"Id\":\"" + id + "\",\"Name\":\"" + name + "\",\"IsScrumTeam\":" + isScrumTeam + "}";
  }
}

====== Building Entity: ======

@EdmEntityType(name = "Building")
@EdmEntitySet(name = "Buildings")
public class Building {
  @EdmKey
  @EdmProperty(type = EdmType.INT32)
  private int id;
  @EdmProperty
  private String name;
  @EdmProperty(name = "Image", type = EdmType.BINARY)
  private byte[] image;
  @EdmNavigationProperty(name = "nb_Rooms", toType = Room.class, association = "BuildingRooms")
  private List<Room> rooms = new ArrayList<Room>();

  public String getId() {
    return Integer.toString(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;
  }

  @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) + "\"}";
  }
Generic In Memory Data Source

====== AnnotationInMemoryDs (implements DataSource) ======

Code snippet for read data access:

public class AnnotationInMemoryDs implements DataSource {

  private static final AnnotationHelper ANNOTATION_HELPER = new AnnotationHelper();
  private final Map<String, DataStore<Object>> dataStores = new HashMap<String, DataStore<Object>>();
  private final boolean persistInMemory;

  public AnnotationInMemoryDs(final Collection<Class<?>> annotatedClasses) throws ODataException {
    this(annotatedClasses, true);
  }

  public AnnotationInMemoryDs(final Collection<Class<?>> annotatedClasses, final boolean persistInMemory) 
      throws ODataException {
    this.persistInMemory = persistInMemory;
    init(annotatedClasses);
  }

  public AnnotationInMemoryDs(final String packageToScan) throws ODataException {
    this(packageToScan, true);
  }

  public AnnotationInMemoryDs(final String packageToScan, final boolean persistInMemory) throws ODataException {
    this.persistInMemory = persistInMemory;
    List<Class<?>> foundClasses = ClassHelper.loadClasses(packageToScan, new ClassHelper.ClassValidator() {
      @Override
      public boolean isClassValid(final Class<?> c) {
        return null != c.getAnnotation(org.apache.olingo.odata2.api.annotation.edm.EdmEntitySet.class);
      }
    });

    init(foundClasses);
  }

  @SuppressWarnings("unchecked")
  private void init(final Collection<Class<?>> annotatedClasses) throws ODataException {
    try {
      for (Class<?> clz : annotatedClasses) {
        DataStore<Object> dhs = (DataStore<Object>) DataStore.createInMemory(clz, persistInMemory);
        String entitySetName = ANNOTATION_HELPER.extractEntitySetName(clz);
        dataStores.put(entitySetName, dhs);
      }
    } catch (DataStore.DataStoreException e) {
      throw new ODataException("Error in DataStore initilization with message: " + e.getMessage(), e);
    }
  }

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

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

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

    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);
  }
  
  /** more internal needed code in Git repo */

====== Generic DataStore ======

Code snippet for generic DataStore (important public method parts)

public class DataStore<T> {

  private static final AnnotationHelper ANNOTATION_HELPER = new AnnotationHelper();
  private final Map<KeyElement, T> dataStore;
  private final Class<T> dataTypeClass;
  private final KeyAccess keyAccess;

  private static class InMemoryDataStore {
    private static final Map<Class<?>, DataStore<?>> c2ds = new HashMap<Class<?>, DataStore<?>>();

    @SuppressWarnings("unchecked")
    static synchronized DataStore<?> getInstance(final Class<?> clz, final boolean createNewInstance)
        throws DataStoreException {
      DataStore<?> ds = c2ds.get(clz);
      if (createNewInstance || ds == null) {
        ds = new DataStore<Object>((Class<Object>) clz);
        c2ds.put(clz, ds);
      }
      return ds;
    }
  }

  @SuppressWarnings("unchecked")
  public static <T> DataStore<T> createInMemory(final Class<T> clazz) throws DataStoreException {
    return (DataStore<T>) InMemoryDataStore.getInstance(clazz, true);
  }

  @SuppressWarnings("unchecked")
  public static <T> DataStore<T> createInMemory(final Class<T> clazz, final boolean keepExisting)
      throws DataStoreException {
    return (DataStore<T>) InMemoryDataStore.getInstance(clazz, !keepExisting);
  }

  private DataStore(final Map<KeyElement, T> wrapStore, final Class<T> clz) throws DataStoreException {
    dataStore = Collections.synchronizedMap(wrapStore);
    dataTypeClass = clz;
    keyAccess = new KeyAccess(clz);
  }

  private DataStore(final Class<T> clz) throws DataStoreException {
    this(new HashMap<KeyElement, T>(), clz);
  }

  public Class<T> getDataTypeClass() {
    return dataTypeClass;
  }

  public String getEntityTypeName() {
    return ANNOTATION_HELPER.extractEntityTypeName(dataTypeClass);
  }

  public T createInstance() {
    try {
      return dataTypeClass.newInstance();
    } catch (InstantiationException e) {
      throw new ODataRuntimeException("Unable to create instance of class '" + dataTypeClass + "'.", e);
    } catch (IllegalAccessException e) {
      throw new ODataRuntimeException("Unable to create instance of class '" + dataTypeClass + "'.", e);
    }
  }

  public T read(final T obj) {
    KeyElement objKeys = getKeys(obj);
    return dataStore.get(objKeys);
  }

  public Collection<T> read() {
    return Collections.unmodifiableCollection(dataStore.values());
  }

  public T create(final T object) throws DataStoreException {
    KeyElement keyElement = getKeys(object);
    return create(object, keyElement);
  }

  private T create(final T object, final KeyElement keyElement) throws DataStoreException {
    synchronized (dataStore) {
      if (keyElement.keyValuesMissing() || dataStore.containsKey(keyElement)) {
        KeyElement newKey = createSetAndGetKeys(object);
        return this.create(object, newKey);
      }
      dataStore.put(keyElement, object);
    }
    return object;
  }

  public T update(final T object) {
    KeyElement keyElement = getKeys(object);
    synchronized (dataStore) {
      dataStore.remove(keyElement);
      dataStore.put(keyElement, object);
    }
    return object;
  }

  public T delete(final T object) {
    KeyElement keyElement = getKeys(object);
    synchronized (dataStore) {
      return dataStore.remove(keyElement);
    }
  }
  
  /** more internal needed code in Git repo */

ODataServiceFactory

====== AnnotationServiceFactoryImpl ======

Code snippet of createService method implementation in which the AnnotationServiceFactory.createAnnotationService(String modelPackage) is used. In this sample the resulting ODataService is hold as a static instance (singleton) to get an persistent service between several calls.

public class AnnotationSampleServiceFactory extends ODataServiceFactory {

  /**
   * Instance holder for all annotation relevant instances which should be used as singleton
   * instances within the ODataApplication (ODataService)
   */
  private static class AnnotationInstances {
    final static String MODEL_PACKAGE = "org.apache.olingo.sample.annotation.model";
    final static ODataService ANNOTATION_ODATA_SERVICE;
    
    static {
      try {
        ANNOTATION_ODATA_SERVICE = AnnotationServiceFactory.createAnnotationService(MODEL_PACKAGE);
      } catch (ODataApplicationException ex) {
        throw new RuntimeException("Exception during sample data generation.", ex);
      } catch (ODataException ex) {
        throw new RuntimeException("Exception during data source initialization generation.", ex);
      }
    }
  }

  @Override
  public ODataService createService(final ODataContext context) throws ODataException {
    // Edm via Annotations and ListProcessor via AnnotationDS with AnnotationsValueAccess
    return AnnotationInstances.ANNOTATION_ODATA_SERVICE;
  }

Code snippet of createAnnotationService method implementation in which the combination of EdmProvider and ODataSingleProcessor are created for the Annotation with the AnnotationEdmProvider and the ListsProcessor which uses the AnnotationInMemoryDs and AnnotationValueAccess via the RuntimeDelegate.createODataSingleProcessorService(...) method.

  public ODataService createAnnotationService(String modelPackage) throws ODataException {
    AnnotationEdmProvider edmProvider = new AnnotationEdmProvider(modelPackage);
    AnnotationInMemoryDs dataSource = new AnnotationInMemoryDs(modelPackage);
    AnnotationValueAccess valueAccess = new AnnotationValueAccess();

    // Edm via Annotations and ListProcessor via AnnotationDS with AnnotationsValueAccess
    return RuntimeDelegate.createODataSingleProcessorService(edmProvider,
        new ListsProcessor(dataSource, valueAccess));
  }

====== AnnotationPocServiceFactory as ODataServiceFactory implementation ======

Code snippet of createService method implementation in which the combination of EdmProvider and ODataSingleProcessor are created for the Annotation with the AnnotationEdmProvider and the ListsProcessor which uses the AnnotationInMemoryDs and AnnotationValueAccess.

  public ODataService createService(final ODataContext context) throws ODataException {

    String modelPackage = "org.apache.olingo.odata2.ref.annotation.model";
    AnnotationEdmProvider annotationEdmProvider = new AnnotationEdmProvider(modelPackage);
    AnnotationInMemoryDs annotationScenarioDs = new AnnotationInMemoryDs(modelPackage);
    AnnotationValueAccess annotationValueAccess = new AnnotationValueAccess();

    if (!isInitialized) {
      initializeSampleData(annotationScenarioDs);
      isInitialized = true;
    }

    // Edm via Annotations and ListProcessor via AnnotationDS with AnnotationsValueAccess
    return createODataSingleProcessorService(annotationEdmProvider,
            new ListsProcessor(annotationScenarioDs, annotationValueAccess));
  }

Sample project via Maven Archetype

With release of Apache Olingo 1.1.0 a Maven Archetype will be available to generate a sample project with a model and a ODataServiceFactory implementation which uses the AnnotationServiceFactory for creation of the ODataService with annotation support.

To generate this sample project run maven with:

mvn archetype:generate 
  -DinteractiveMode=false
  -Dversion=1.0.0-SNAPSHOT 
  -DgroupId=com.sample
  -DartifactId=my-car-service 
  -DarchetypeGroupId=org.apache.olingo 
  -DarchetypeArtifactId=olingo-odata2-sample-cars-annotation-archetype-incubating 
  -DarchetypeVersion=1.1.0-SNAPSHOT

Afterward an Jetty web server can be started with running the default goal via mvn within the project. The service can than be requested via http://localhost:8080.

Additionally an eclipse project can be created with running mvn eclipse:eclipse within the project.

Basic tutorial

As basic tutorial the current recommendation is to take a look into the Apache Olingo project in the sub module olingo-odata2-edm-annotation-webref in package org.apache.olingo.odata2.ref.annotation and below. In the package org.apache.olingo.odata2.ref.annotation.model and below is the model defined and in package org.apache.olingo.odata2.ref.annotation.processor and below is the service factory. All around is mainly necessary to package all into a deployable WAR file.

  • No labels