Dear Wiki user, You have subscribed to a wiki page or wiki category on "Olingo Wiki" for change notification.
The "Documentation/AnnotationProcessor" page has been changed by MichaelBolz: https://wiki.apache.org/Olingo/Documentation/AnnotationProcessor?action=diff&rev1=2&rev2=3 Comment: Add sample code 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}}}. + + {{{ + @Override + 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. +
