Differences between revisions 5 and 6
Revision 5 as of 2014-01-15 10:10:38
Size: 34051
Editor: MichaelBolz
Comment: Fixed (some) links
Revision 6 as of 2014-01-15 10:17:29
Size: 34049
Editor: MichaelBolz
Comment:
Deletions are marked like this. Additions are marked like this.
Line 1: Line 1:
<<TableOfContents(2)>>
Line 74: Line 76:
An architecture architecture overview is shown [here|^ArchitectureOverview.svg] and below:
!ArchitectureOverview.png|align=left,thumbnail,border=1!

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].
An architecture architecture overview is shown here: [[attachment:ArchitectureOverview.svg]].
An overview about involved classes is shown in this class diagram: [[attachment:ClassDiagram.svg]].

In addition to the here shown Java code more documentation can be found in the Javadoc of "odata2-api-annotation": [[attachment:api-annotation-apidocs.zip]] and Javadoc of "odata2-annotation-processor-api": [[attachment:annotation-proc-api-apidocs.zip]].
Line 307: Line 307:
== POC realization ==

=== Edm Provider and Json Processor for annotated model ===
= POC realization =

== Edm Provider and Json Processor for annotated model ==
Line 314: Line 314:
=== Generic data access (via ListsProcessor, ValueAccess, GenericDs) === == Generic data access (via ListsProcessor, ValueAccess, GenericDs) ==
Line 729: Line 729:
== Sample project via Maven Archetype == = Sample project via Maven Archetype =
Line 749: Line 749:
== Basic tutorial == = Basic tutorial =

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

   1 /**
   2  * Abstract class for implementation of the centralized parts of OData processing,
   3  * allowing to use the simplified {@link DataSource} and {@link ValueAccess} for the
   4  * actual data handling.
   5  * <br/>
   6  * Extend this class and implement a DataSourceProcessor if the default implementation
   7  * (<code>ListProcessor</code> found in <code>annotation-processor-core module</code>) has to be overwritten.
   8  */
   9 public abstract class DataSourceProcessor extends ODataSingleProcessor {
  10 
  11   protected final DataSource dataSource;
  12   protected final ValueAccess valueAccess;
  13   
  14   /**
  15    * Initialize a {@link DataSourceProcessor} in combination with given {@link DataSource} (providing data objects)
  16    * and {@link ValueAccess} (accessing values of data objects).
  17    * 
  18    * @param dataSource used for accessing the data objects
  19    * @param valueAccess for accessing the values provided by the data objects
  20    */
  21   public DataSourceProcessor(final DataSource dataSource, final ValueAccess valueAccess) {
  22     this.dataSource = dataSource;
  23     this.valueAccess = valueAccess;
  24   }
  25 }

DataSource

   1 public interface DataSource {
   2 
   3   /**
   4    * Retrieves the whole data list for the specified entity set.
   5    * @param entitySet the requested {@link EdmEntitySet}
   6    * @return the requested data list
   7    */
   8   List<?> readData(EdmEntitySet entitySet) throws ODataNotImplementedException, ODataNotFoundException, EdmException,
   9       ODataApplicationException;
  10 
  11   /**
  12    * Retrieves a single data object for the specified entity set and key.
  13    * @param entitySet the requested {@link EdmEntitySet}
  14    * @param keys the entity key as map of key names to key values
  15    * @return the requested data object
  16    */
  17   Object readData(EdmEntitySet entitySet, Map<String, Object> keys) throws ODataNotImplementedException,
  18       ODataNotFoundException, EdmException, ODataApplicationException;
  19 
  20   /**
  21    * <p>Retrieves data for the specified function import and key.</p>
  22    * <p>This method is called also for function imports that have defined in
  23    * their metadata an other HTTP method than <code>GET</code>.</p>
  24    * @param function the requested {@link EdmFunctionImport}
  25    * @param parameters the parameters of the function import
  26    * as map of parameter names to parameter values
  27    * @param keys the key of the returned entity set, as map of key names to key values,
  28    * if the return type of the function import is a collection of entities
  29    * (optional)
  30    * @return the requested data object, either a list or a single object;
  31    * if the function import's return type is of type <code>Binary</code>,
  32    * the returned object(s) must be of type {@link BinaryData}
  33    */
  34   Object readData(EdmFunctionImport function, Map<String, Object> parameters, Map<String, Object> keys)
  35       throws ODataNotImplementedException, ODataNotFoundException, EdmException, ODataApplicationException;
  36 
  37   /**
  38    * <p>Retrieves related data for the specified source data, entity set, and key.</p>
  39    * <p>If the underlying association of the EDM is specified to have target
  40    * multiplicity '*' and no target key is given, this method returns a list of
  41    * related data, otherwise it returns a single data object.</p>
  42    * @param sourceEntitySet the {@link EdmEntitySet} of the source entity
  43    * @param sourceData the data object of the source entity
  44    * @param targetEntitySet the requested target {@link EdmEntitySet}
  45    * @param targetKeys the key of the target entity as map of key names to key values
  46    * (optional)
  47    * @return the requested releated data object, either a list or a single object
  48    */
  49   Object readRelatedData(EdmEntitySet sourceEntitySet, Object sourceData, EdmEntitySet targetEntitySet,
  50       Map<String, Object> targetKeys) throws ODataNotImplementedException, ODataNotFoundException, EdmException,
  51       ODataApplicationException;
  52 
  53   /**
  54    * Retrieves the binary data and the MIME type for the media resource
  55    * associated to the specified media-link entry.
  56    * @param entitySet the {@link EdmEntitySet} of the media-link entry
  57    * @param mediaLinkEntryData the data object of the media-link entry
  58    * @return the binary data and the MIME type of the media resource
  59    */
  60   BinaryData readBinaryData(EdmEntitySet entitySet, Object mediaLinkEntryData) throws ODataNotImplementedException,
  61       ODataNotFoundException, EdmException, ODataApplicationException;
  62 
  63   /**
  64    * <p>Creates and returns a new instance of the requested data-object type.</p>
  65    * <p>This instance must not be part of the corresponding list and should
  66    * have empty content, apart from the key and other mandatory properties.
  67    * However, intermediate objects to access complex properties must not be
  68    * <code>null</code>.</p>
  69    * @param entitySet the {@link EdmEntitySet} the object must correspond to
  70    * @return the new data object
  71    */
  72   Object newDataObject(EdmEntitySet entitySet) throws ODataNotImplementedException, EdmException,
  73       ODataApplicationException;
  74 
  75   /**
  76    * Writes the binary data for the media resource associated to the
  77    * specified media-link entry.
  78    * @param entitySet the {@link EdmEntitySet} of the media-link entry
  79    * @param mediaLinkEntryData the data object of the media-link entry
  80    * @param binaryData the binary data of the media resource along with
  81    * the MIME type of the binary data
  82    */
  83   void writeBinaryData(EdmEntitySet entitySet, Object mediaLinkEntryData, BinaryData binaryData)
  84       throws ODataNotImplementedException, ODataNotFoundException, EdmException, ODataApplicationException;
  85 
  86   /**
  87    * Deletes a single data object identified by the specified entity set and key.
  88    * @param entitySet the {@link EdmEntitySet} of the entity to be deleted
  89    * @param keys the entity key as map of key names to key values
  90    */
  91   void deleteData(EdmEntitySet entitySet, Map<String, Object> keys) throws ODataNotImplementedException,
  92       ODataNotFoundException, EdmException, ODataApplicationException;
  93 
  94   /**
  95    * <p>Inserts an instance into the entity list of the specified entity set.</p>
  96    * <p>If {@link #newDataObject} has not set the key and other mandatory
  97    * properties already, this method must set them before inserting the
  98    * instance into the list.</p>
  99    * @param entitySet the {@link EdmEntitySet} the object must correspond to
 100    * @param data the data object of the new entity
 101    */
 102   void createData(EdmEntitySet entitySet, Object data) throws ODataNotImplementedException, EdmException,
 103       ODataApplicationException;
 104 
 105   /**
 106    * Deletes the relation from the specified source data to a target entity
 107    * specified by entity set and key.
 108    * @param sourceEntitySet the {@link EdmEntitySet} of the source entity
 109    * @param sourceData the data object of the source entity
 110    * @param targetEntitySet the {@link EdmEntitySet} of the target entity
 111    * @param targetKeys the key of the target entity as map of key names to key values
 112    * (optional)
 113    */
 114   void deleteRelation(EdmEntitySet sourceEntitySet, Object sourceData, EdmEntitySet targetEntitySet,
 115       Map<String, Object> targetKeys) throws ODataNotImplementedException, ODataNotFoundException, EdmException,
 116       ODataApplicationException;
 117 
 118   /**
 119    * Writes a relation from the specified source data to a target entity
 120    * specified by entity set and key.
 121    * @param sourceEntitySet the {@link EdmEntitySet} of the source entity
 122    * @param sourceData the data object of the source entity
 123    * @param targetEntitySet the {@link EdmEntitySet} of the relation target
 124    * @param targetKeys the key of the target entity as map of key names to key values
 125    */
 126   void writeRelation(EdmEntitySet sourceEntitySet, Object sourceData, EdmEntitySet targetEntitySet,
 127       Map<String, Object> targetKeys) throws ODataNotImplementedException, ODataNotFoundException, EdmException,
 128       ODataApplicationException;

ValueAccess

   1 /**
   2  * This interface is intended to access values in a Java object.
   3  */
   4 public interface ValueAccess {
   5 
   6   /**
   7    * Retrieves the value of an EDM property for the given data object.
   8    * @param data     the Java data object
   9    * @param property the requested {@link EdmProperty}
  10    * @return the requested property value
  11    */
  12   public <T> Object getPropertyValue(final T data, final EdmProperty property) throws ODataException;
  13 
  14   /**
  15    * Sets the value of an EDM property for the given data object.
  16    * @param data     the Java data object
  17    * @param property the {@link EdmProperty}
  18    * @param value    the new value of the property
  19    */
  20   public <T, V> void setPropertyValue(T data, final EdmProperty property, final V value) throws ODataException;
  21 
  22   /**
  23    * Retrieves the Java type of an EDM property for the given data object.
  24    * @param data     the Java data object
  25    * @param property the requested {@link EdmProperty}
  26    * @return the requested Java type
  27    */
  28   public <T> Class<?> getPropertyType(final T data, final EdmProperty property) throws ODataException;
  29 
  30   /**
  31    * Retrieves the value defined by a mapping object for the given data object.
  32    * @param data     the Java data object
  33    * @param mapping  the requested {@link EdmMapping}
  34    * @return the requested value
  35    */
  36   public <T> Object getMappingValue(final T data, final EdmMapping mapping) throws ODataException;
  37 
  38   /**
  39    * Sets the value defined by a mapping object for the given data object.
  40    * @param data     the Java data object
  41    * @param mapping  the {@link EdmMapping}
  42    * @param value    the new value
  43    */
  44   public <T, V> void setMappingValue(T data, final EdmMapping mapping, final V value) throws ODataException;
  45 }

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:

   1 /**
   2  *
   3  */
   4 @EdmEntityType(name="Base")
   5 public abstract class RefBase {
   6   @EdmProperty(name="Name")
   7   protected String name;
   8   @EdmKey
   9   @EdmProperty(name="Id", type = EdmType.STRING)
  10   protected int id;
  11 
  12   public String getName() {
  13     return name;
  14   }
  15 
  16   public String getId() {
  17     return Integer.toString(id);
  18   }
  19 
  20   public void setName(String name) {
  21     this.name = name;
  22   }
  23 
  24   public void setId(int id) {
  25     this.id = id;
  26   }
  27 }

Team Entity:

   1 @EdmEntityType(name = "Team")
   2 @EdmEntitySet(name = "Teams")
   3 public class Team extends RefBase {
   4   @EdmProperty(type = EdmType.BOOLEAN)
   5   private Boolean isScrumTeam;
   6   @EdmNavigationProperty(name = "nt_Employees", association = "TeamEmployees")
   7   private List<Employee> employees = new ArrayList<Employee>();
   8 
   9   public Boolean isScrumTeam() {
  10     return isScrumTeam;
  11   }
  12 
  13   public void setScrumTeam(final Boolean isScrumTeam) {
  14     this.isScrumTeam = isScrumTeam;
  15   }
  16   
  17   public void addEmployee(Employee e) {
  18     this.employees.add(e);
  19   }
  20 
  21   public List<Employee> getEmployees() {
  22     return employees;
  23   }
  24 
  25   @Override
  26   public int hashCode() {
  27     return id;
  28   }
  29 
  30   @Override
  31   public boolean equals(final Object obj) {
  32     return this == obj
  33         || obj != null && getClass() == obj.getClass() && id == ((Team) obj).id;
  34   }
  35 
  36   @Override
  37   public String toString() {
  38     return "{\"Id\":\"" + id + "\",\"Name\":\"" + name + "\",\"IsScrumTeam\":" + isScrumTeam + "}";
  39   }
  40 }

Building Entity:

   1 @EdmEntityType(name = "Building")
   2 @EdmEntitySet(name = "Buildings")
   3 public class Building {
   4   @EdmKey
   5   @EdmProperty(type = EdmType.INT32)
   6   private int id;
   7   @EdmProperty
   8   private String name;
   9   @EdmProperty(name = "Image", type = EdmType.BINARY)
  10   private byte[] image;
  11   @EdmNavigationProperty(name = "nb_Rooms", toType = Room.class, association = "BuildingRooms")
  12   private List<Room> rooms = new ArrayList<Room>();
  13 
  14   public String getId() {
  15     return Integer.toString(id);
  16   }
  17 
  18   public void setName(final String name) {
  19     this.name = name;
  20   }
  21 
  22   public String getName() {
  23     return name;
  24   }
  25 
  26   public void setImage(final byte[] byteArray) {
  27     image = byteArray;
  28   }
  29 
  30   public byte[] getImage() {
  31     if (image == null) {
  32       return null;
  33     } else {
  34       return image.clone();
  35     }
  36   }
  37 
  38   public List<Room> getRooms() {
  39     return rooms;
  40   }
  41 
  42   @Override
  43   public int hashCode() {
  44     return id;
  45   }
  46 
  47   @Override
  48   public boolean equals(final Object obj) {
  49     return this == obj
  50         || obj != null && getClass() == obj.getClass() && id == ((Building) obj).id;
  51   }
  52 
  53   @Override
  54   public String toString() {
  55     return "{\"Id\":\"" + id + "\",\"Name\":\"" + name + "\",\"Image\":\"" + Arrays.toString(image) + "\"}";
  56   }

Generic In Memory Data Source

AnnotationInMemoryDs (implements DataSource)

Code snippet for read data access:

   1 public class AnnotationInMemoryDs implements DataSource {
   2 
   3   private static final AnnotationHelper ANNOTATION_HELPER = new AnnotationHelper();
   4   private final Map<String, DataStore<Object>> dataStores = new HashMap<String, DataStore<Object>>();
   5   private final boolean persistInMemory;
   6 
   7   public AnnotationInMemoryDs(final Collection<Class<?>> annotatedClasses) throws ODataException {
   8     this(annotatedClasses, true);
   9   }
  10 
  11   public AnnotationInMemoryDs(final Collection<Class<?>> annotatedClasses, final boolean persistInMemory) 
  12       throws ODataException {
  13     this.persistInMemory = persistInMemory;
  14     init(annotatedClasses);
  15   }
  16 
  17   public AnnotationInMemoryDs(final String packageToScan) throws ODataException {
  18     this(packageToScan, true);
  19   }
  20 
  21   public AnnotationInMemoryDs(final String packageToScan, final boolean persistInMemory) throws ODataException {
  22     this.persistInMemory = persistInMemory;
  23     List<Class<?>> foundClasses = ClassHelper.loadClasses(packageToScan, new ClassHelper.ClassValidator() {
  24       @Override
  25       public boolean isClassValid(final Class<?> c) {
  26         return null != c.getAnnotation(org.apache.olingo.odata2.api.annotation.edm.EdmEntitySet.class);
  27       }
  28     });
  29 
  30     init(foundClasses);
  31   }
  32 
  33   @SuppressWarnings("unchecked")
  34   private void init(final Collection<Class<?>> annotatedClasses) throws ODataException {
  35     try {
  36       for (Class<?> clz : annotatedClasses) {
  37         DataStore<Object> dhs = (DataStore<Object>) DataStore.createInMemory(clz, persistInMemory);
  38         String entitySetName = ANNOTATION_HELPER.extractEntitySetName(clz);
  39         dataStores.put(entitySetName, dhs);
  40       }
  41     } catch (DataStore.DataStoreException e) {
  42       throw new ODataException("Error in DataStore initilization with message: " + e.getMessage(), e);
  43     }
  44   }
  45 
  46   @SuppressWarnings("unchecked")
  47   public <T> DataStore<T> getDataStore(final Class<T> clazz) {
  48     String entitySetName = ANNOTATION_HELPER.extractEntitySetName(clazz);
  49     return (DataStore<T>) dataStores.get(entitySetName);
  50   }
  51 
  52   @Override
  53   public List<?> readData(final EdmEntitySet entitySet) throws ODataNotImplementedException,
  54       ODataNotFoundException, EdmException, ODataApplicationException {
  55 
  56     DataStore<Object> holder = getDataStore(entitySet);
  57     if (holder != null) {
  58       return new ArrayList<Object>(holder.read());
  59     }
  60 
  61     throw new ODataNotFoundException(ODataNotFoundException.ENTITY);
  62   }
  63 
  64   @Override
  65   public Object readData(final EdmEntitySet entitySet, final Map<String, Object> keys)
  66       throws ODataNotFoundException, EdmException, ODataApplicationException {
  67 
  68     DataStore<Object> store = getDataStore(entitySet);
  69     if (store != null) {
  70       Object keyInstance = store.createInstance();
  71       ANNOTATION_HELPER.setKeyFields(keyInstance, keys);
  72 
  73       Object result = store.read(keyInstance);
  74       if (result != null) {
  75         return result;
  76       }
  77     }
  78 
  79     throw new ODataNotFoundException(ODataNotFoundException.ENTITY);
  80   }
  81   
  82   /** more internal needed code in Git repo */

Generic DataStore

Code snippet for generic DataStore (important public method parts)

   1 public class DataStore<T> {
   2 
   3   private static final AnnotationHelper ANNOTATION_HELPER = new AnnotationHelper();
   4   private final Map<KeyElement, T> dataStore;
   5   private final Class<T> dataTypeClass;
   6   private final KeyAccess keyAccess;
   7 
   8   private static class InMemoryDataStore {
   9     private static final Map<Class<?>, DataStore<?>> c2ds = new HashMap<Class<?>, DataStore<?>>();
  10 
  11     @SuppressWarnings("unchecked")
  12     static synchronized DataStore<?> getInstance(final Class<?> clz, final boolean createNewInstance)
  13         throws DataStoreException {
  14       DataStore<?> ds = c2ds.get(clz);
  15       if (createNewInstance || ds == null) {
  16         ds = new DataStore<Object>((Class<Object>) clz);
  17         c2ds.put(clz, ds);
  18       }
  19       return ds;
  20     }
  21   }
  22 
  23   @SuppressWarnings("unchecked")
  24   public static <T> DataStore<T> createInMemory(final Class<T> clazz) throws DataStoreException {
  25     return (DataStore<T>) InMemoryDataStore.getInstance(clazz, true);
  26   }
  27 
  28   @SuppressWarnings("unchecked")
  29   public static <T> DataStore<T> createInMemory(final Class<T> clazz, final boolean keepExisting)
  30       throws DataStoreException {
  31     return (DataStore<T>) InMemoryDataStore.getInstance(clazz, !keepExisting);
  32   }
  33 
  34   private DataStore(final Map<KeyElement, T> wrapStore, final Class<T> clz) throws DataStoreException {
  35     dataStore = Collections.synchronizedMap(wrapStore);
  36     dataTypeClass = clz;
  37     keyAccess = new KeyAccess(clz);
  38   }
  39 
  40   private DataStore(final Class<T> clz) throws DataStoreException {
  41     this(new HashMap<KeyElement, T>(), clz);
  42   }
  43 
  44   public Class<T> getDataTypeClass() {
  45     return dataTypeClass;
  46   }
  47 
  48   public String getEntityTypeName() {
  49     return ANNOTATION_HELPER.extractEntityTypeName(dataTypeClass);
  50   }
  51 
  52   public T createInstance() {
  53     try {
  54       return dataTypeClass.newInstance();
  55     } catch (InstantiationException e) {
  56       throw new ODataRuntimeException("Unable to create instance of class '" + dataTypeClass + "'.", e);
  57     } catch (IllegalAccessException e) {
  58       throw new ODataRuntimeException("Unable to create instance of class '" + dataTypeClass + "'.", e);
  59     }
  60   }
  61 
  62   public T read(final T obj) {
  63     KeyElement objKeys = getKeys(obj);
  64     return dataStore.get(objKeys);
  65   }
  66 
  67   public Collection<T> read() {
  68     return Collections.unmodifiableCollection(dataStore.values());
  69   }
  70 
  71   public T create(final T object) throws DataStoreException {
  72     KeyElement keyElement = getKeys(object);
  73     return create(object, keyElement);
  74   }
  75 
  76   private T create(final T object, final KeyElement keyElement) throws DataStoreException {
  77     synchronized (dataStore) {
  78       if (keyElement.keyValuesMissing() || dataStore.containsKey(keyElement)) {
  79         KeyElement newKey = createSetAndGetKeys(object);
  80         return this.create(object, newKey);
  81       }
  82       dataStore.put(keyElement, object);
  83     }
  84     return object;
  85   }
  86 
  87   public T update(final T object) {
  88     KeyElement keyElement = getKeys(object);
  89     synchronized (dataStore) {
  90       dataStore.remove(keyElement);
  91       dataStore.put(keyElement, object);
  92     }
  93     return object;
  94   }
  95 
  96   public T delete(final T object) {
  97     KeyElement keyElement = getKeys(object);
  98     synchronized (dataStore) {
  99       return dataStore.remove(keyElement);
 100     }
 101   }
 102   
 103   /** 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.

   1 public class AnnotationSampleServiceFactory extends ODataServiceFactory {
   2 
   3   /**
   4    * Instance holder for all annotation relevant instances which should be used as singleton
   5    * instances within the ODataApplication (ODataService)
   6    */
   7   private static class AnnotationInstances {
   8     final static String MODEL_PACKAGE = "org.apache.olingo.sample.annotation.model";
   9     final static ODataService ANNOTATION_ODATA_SERVICE;
  10     
  11     static {
  12       try {
  13         ANNOTATION_ODATA_SERVICE = AnnotationServiceFactory.createAnnotationService(MODEL_PACKAGE);
  14       } catch (ODataApplicationException ex) {
  15         throw new RuntimeException("Exception during sample data generation.", ex);
  16       } catch (ODataException ex) {
  17         throw new RuntimeException("Exception during data source initialization generation.", ex);
  18       }
  19     }
  20   }
  21 
  22   @Override
  23   public ODataService createService(final ODataContext context) throws ODataException {
  24     // Edm via Annotations and ListProcessor via AnnotationDS with AnnotationsValueAccess
  25     return AnnotationInstances.ANNOTATION_ODATA_SERVICE;
  26   }

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.

   1   public ODataService createAnnotationService(String modelPackage) throws ODataException {
   2     AnnotationEdmProvider edmProvider = new AnnotationEdmProvider(modelPackage);
   3     AnnotationInMemoryDs dataSource = new AnnotationInMemoryDs(modelPackage);
   4     AnnotationValueAccess valueAccess = new AnnotationValueAccess();
   5 
   6     // Edm via Annotations and ListProcessor via AnnotationDS with AnnotationsValueAccess
   7     return RuntimeDelegate.createODataSingleProcessorService(edmProvider,
   8         new ListsProcessor(dataSource, valueAccess));
   9   }

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.

   1   public ODataService createService(final ODataContext context) throws ODataException {
   2 
   3     String modelPackage = "org.apache.olingo.odata2.ref.annotation.model";
   4     AnnotationEdmProvider annotationEdmProvider = new AnnotationEdmProvider(modelPackage);
   5     AnnotationInMemoryDs annotationScenarioDs = new AnnotationInMemoryDs(modelPackage);
   6     AnnotationValueAccess annotationValueAccess = new AnnotationValueAccess();
   7 
   8     if (!isInitialized) {
   9       initializeSampleData(annotationScenarioDs);
  10       isInitialized = true;
  11     }
  12 
  13     // Edm via Annotations and ListProcessor via AnnotationDS with AnnotationsValueAccess
  14     return createODataSingleProcessorService(annotationEdmProvider,
  15             new ListsProcessor(annotationScenarioDs, annotationValueAccess));
  16   }

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.

Documentation/AnnotationProcessor (last edited 2014-01-15 10:17:29 by MichaelBolz)