Skip to content

Commit

Permalink
- improve type support for query response customizer
Browse files Browse the repository at this point in the history
  • Loading branch information
Ralf Zozmann committed Feb 3, 2025
1 parent 16288a4 commit 81b9094
Show file tree
Hide file tree
Showing 22 changed files with 353 additions and 179 deletions.
4 changes: 4 additions & 0 deletions doc/integrators/MigrationGuide.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
[Overview](TableOfContent.md)

---
# 1.1.1/2.1.1 → 1.2.0/2.2.0
## Breaking change for `org.apache.olingo.jpa.processor.core.mapping.JPAAdapter` and extending classes
The method to register DTO entity types was renamed from `registerDTO(...)` to `registerDTOEntityType(...)`. Same for getter: `getDTOs()` was renamed to `getDTOEntityTypes()`.

# 1.0.0/2.0.0 → 1.1.0/2.1.0
## Namespace for maven artifacts changed
The _groupId_ for all maven artifacts has changed from `org.apache.olingo.jpa` to `de.exxcellent.odata.jpa`. So the dependencies to use the library has to be adapted like that example:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.apache.olingo.jpa.processor.core.mapping.ResourceLocalPersistenceAdapter;
import org.apache.olingo.jpa.processor.core.security.AnnotationBasedSecurityInceptor;
import org.apache.olingo.jpa.processor.core.testmodel.dto.EnvironmentInfo;
import org.apache.olingo.jpa.processor.core.testmodel.dto.RecordAsComplexType;
import org.apache.olingo.jpa.processor.core.testmodel.dto.sub.SystemRequirement;
import org.apache.olingo.jpa.test.util.DataSourceHelper;
import org.apache.olingo.server.api.ODataResponse;
Expand Down Expand Up @@ -92,8 +93,9 @@ private JPAODataServletHandler createHandler() throws ODataException, ServletExc
org.apache.olingo.jpa.test.util.Constant.PUNIT_NAME,
elProperties,
new JPA_DERBYDatabaseProcessor());
mappingAdapter.registerDTO(EnvironmentInfo.class);
mappingAdapter.registerDTO(SystemRequirement.class);
mappingAdapter.registerDTOEntityType(EnvironmentInfo.class);
mappingAdapter.registerDTOEntityType(SystemRequirement.class);
mappingAdapter.registerDTOComplexType(RecordAsComplexType.class);

final JPAODataServletHandler handler = new JPAODataServletHandler(mappingAdapter) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,6 @@ List<JPAComplexType<?>> getComplexTypes() {
return new ArrayList<JPAComplexType<?>>(complexTypes.values());
}

@SuppressWarnings("unchecked")
<X> IntermediateEnityTypeDTO<X> getDTOType(final Class<X> targetClass) {
return (IntermediateEnityTypeDTO<X>) dtoTypes.get(getNameBuilder().buildDTOTypeName(targetClass));
}

protected final void lazyBuildEdmItem() throws ODataJPAModelException {
if (edmSchema != null) {
return;
Expand Down Expand Up @@ -131,21 +126,20 @@ IntermediateEnumType findOrCreateEnumType(final Class<? extends Enum<?>> clazz)
* The map may be part of an schema (namespace), but as representation for {@link java.util.Map} the related namespace
* should be used.
*/
IntermediateMapComplexTypeDTO createDynamicMapType(final Class<?> mapKeyType, final Class<?> mapValueType,
<Y> IntermediateMapComplexTypeDTO<Y> createDynamicMapType(final Class<Y> mapValueType,
final boolean valueIsCollection) throws ODataJPAModelException {
final String simpleName = Map.class.getSimpleName() + "{" + Integer.toString(++dtCount) + "}";
if (complexTypes.containsKey(simpleName))
final String dtName = Map.class.getSimpleName() + "Type{" + Integer.toString(++dtCount) + "}";
if (complexTypes.containsKey(dtName))
{
throw new ODataJPAModelException(MessageKeys.RUNTIME_PROBLEM);
}
// define a Map DTO type... the map will have no attributes (mostly EdmUntyped), because all attributes are dynamic.
final IntermediateMapComplexTypeDTO mapType = new IntermediateMapComplexTypeDTO(getNameBuilder(), simpleName,
mapKeyType, mapValueType,
final IntermediateMapComplexTypeDTO<Y> mapType = new IntermediateMapComplexTypeDTO<>(getNameBuilder(), dtName, mapValueType,
valueIsCollection, serviceDocument);
if (!mapWarningAlreadyLogged) {
mapWarningAlreadyLogged = true;
LOGGER.info("The type " + Map.class.getCanonicalName()
+ " was created as complex open type. Open types are not supported by Olingo's (de)serializer, so a custom (de)serializer by OData-JPA-Adapter must be used. There is only JSON supported!");
LOGGER.info("The type " + dtName
+ " was created as complex (open) type. Open types are not supported by Olingo's (de)serializer, so a custom (de)serializer by OData-JPA-Adapter must be used. There is only JSON supported!");
}
complexTypes.put(mapType.getExternalName(), mapType);
// force rebuild
Expand All @@ -169,13 +163,13 @@ <X> AbstractIntermediateComplexTypeDTO<X> findOrCreateDTOComplexType(final Class
return complexType;
}

<X> IntermediateEnityTypeDTO<X> findOrCreateDTOType(final Class<X> clazz) throws ODataJPAModelException {
<X> IntermediateEnityTypeDTO<X> findOrCreateDTOEntityType(final Class<X> clazz) throws ODataJPAModelException {
final String namespace = clazz.getPackage().getName();
if (!namespace.equalsIgnoreCase(getInternalName())) {
throw new ODataJPAModelException(MessageKeys.GENERAL);
}

IntermediateEnityTypeDTO<X> dtoType = getDTOType(clazz);
IntermediateEnityTypeDTO<X> dtoType = (IntermediateEnityTypeDTO<X>)getEntityType(clazz);
if (dtoType == null) {
dtoType = new IntermediateEnityTypeDTO<X>(getNameBuilder(), clazz, serviceDocument);
dtoTypes.put(dtoType.getExternalName(), dtoType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,15 @@
import org.apache.olingo.jpa.metadata.core.edm.mapper.api.JPAStructuredType;
import org.apache.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelException;

class IntermediateMapComplexTypeDTO extends AbstractIntermediateComplexTypeDTO<Map> implements JPADynamicPropertyContainer {
class IntermediateMapComplexTypeDTO<Y> extends AbstractIntermediateComplexTypeDTO<Map<String, Y>> implements JPADynamicPropertyContainer {
final private EdmPrimitiveTypeKind mapValueKind;
final private DynamicJPAParameterizedElement dynamicProperty;

public IntermediateMapComplexTypeDTO(final JPAEdmNameBuilder nameBuilder, final String typeName,
final Class<?> mapKeyType, final Class<?> mapValueType, final boolean valueIsCollection,
public IntermediateMapComplexTypeDTO(final JPAEdmNameBuilder nameBuilder, final String typeName, final Class<Y> mapValueType, final boolean valueIsCollection,
final IntermediateServiceDocument serviceDocument)
throws ODataJPAModelException {
super(nameBuilder, typeName, true, true, serviceDocument);
this.setExternalName(typeName);
if (!String.class.isAssignableFrom(mapKeyType)) {
throw new ODataJPAModelException(ODataJPAModelException.MessageKeys.INVALID_PARAMETER,
"Map key parameter " + mapKeyType.getTypeName() + " must be a String");
}
try {
// provoke exception for not simple types
this.mapValueKind = TypeMapping.convertToEdmSimpleType(mapValueType);
Expand All @@ -52,9 +47,10 @@ void setAnnotatedElement(final AnnotatedElement element) {
dynamicProperty.setAnnotatedElement(element);
}

@SuppressWarnings("unchecked")
@Override
public Class<Map> getTypeClass() {
return Map.class;
public Class<Map<String, Y>> getTypeClass() {
return (Class<Map<String,Y>>)(Class<?>) Map.class;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,8 @@ private static FullQualifiedName findOrCreateType(final IntermediateServiceDocum
final Class<?> typeClass = TypeMapping.determineClassType(collectionUnwrappedType);
if (Map.class.isAssignableFrom(typeClass)) {
// map as dynamic type?
final Triple<Class<?>, Class<?>, Boolean> typeInfo = checkMapTypeArgumentsMustBeSimple(type, typeOwnerName);
final IntermediateMapComplexTypeDTO jpaMapType = serviceDocument.createDynamicJavaUtilMapType(typeInfo
.getLeft(), typeInfo.getMiddle(), typeInfo.getRight().booleanValue());
final Triple<Class<String>, Class<?>, Boolean> typeInfo = checkMapTypeArgumentsMustBeSimple(type, typeOwnerName);
final IntermediateMapComplexTypeDTO<?> jpaMapType = serviceDocument.createDynamicJavaUtilMapType(typeInfo.getMiddle(), typeInfo.getRight().booleanValue());
jpaMapType.setAnnotatedElement(typeOwner);
return jpaMapType.getExternalFQN();
} else if (typeClass.getAnnotation(ODataDTO.class) != null || isTargetingJPA(serviceDocument, typeClass)) {
Expand Down Expand Up @@ -202,7 +201,8 @@ private static boolean isTargetingJPA(final IntermediateServiceDocument serviceD
return schema.getStructuredType(typeClass) != null;
}

static Triple<Class<?>, Class<?>, Boolean> checkMapTypeArgumentsMustBeSimple(final Type theType,
@SuppressWarnings("unchecked")
static Triple<Class<String>, Class<?>, Boolean> checkMapTypeArgumentsMustBeSimple(final Type theType,
final String elementNameForErrorMessages)
throws ODataJPAModelException {
if (!ParameterizedType.class.isInstance(theType)) {
Expand All @@ -216,11 +216,15 @@ static Triple<Class<?>, Class<?>, Boolean> checkMapTypeArgumentsMustBeSimple(fin
"Map<x,y>, having two type arguments expected");
}
final Type keyType = extractTypeOfGenericType(typeArguments[0]);
if (!Class.class.isInstance(keyType) || !String.class.isAssignableFrom((Class<?>)keyType)) {
throw new ODataJPAModelException(ODataJPAModelException.MessageKeys.INVALID_PARAMETER,
"Map key parameter " + keyType.getTypeName() + " must be a String");
}
final Type valueType = extractTypeOfGenericType(typeArguments[1]);
final boolean isCollection = Class.class.isInstance(typeArguments[1]) && Collection.class.isAssignableFrom(
Class.class.cast(typeArguments[1])) || ParameterizedType.class.isInstance(typeArguments[1]) && Collection.class
.isAssignableFrom((Class<?>) ParameterizedType.class.cast(typeArguments[1]).getRawType());
return new Triple<Class<?>, Class<?>, Boolean>(Class.class.cast(keyType), Class.class.cast(valueType), Boolean
return new Triple<Class<String>, Class<?>, Boolean>(Class.class.cast(keyType), Class.class.cast(valueType), Boolean
.valueOf(isCollection));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
import java.util.List;
import java.util.Map;

import jakarta.persistence.metamodel.Metamodel;

import org.apache.olingo.commons.api.edm.EdmAction;
import org.apache.olingo.commons.api.edm.EdmFunction;
import org.apache.olingo.commons.api.edm.EdmType;
Expand All @@ -16,14 +14,15 @@
import org.apache.olingo.commons.api.edm.provider.CsdlEntityContainerInfo;
import org.apache.olingo.commons.api.edm.provider.CsdlSchema;
import org.apache.olingo.jpa.metadata.core.edm.mapper.api.JPAAction;
import org.apache.olingo.jpa.metadata.core.edm.mapper.api.JPAElement;
import org.apache.olingo.jpa.metadata.core.edm.mapper.api.JPAEntitySet;
import org.apache.olingo.jpa.metadata.core.edm.mapper.api.JPAEntityType;
import org.apache.olingo.jpa.metadata.core.edm.mapper.api.JPAFunction;
import org.apache.olingo.jpa.metadata.core.edm.mapper.api.JPAStructuredType;
import org.apache.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelException;
import org.apache.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelException.MessageKeys;

import jakarta.persistence.metamodel.Metamodel;

/*
* http://docs.oasis-open.org/odata/odata/v4.0/errata02/os/complete/schemas/edmx.xsd
* A Service Document can contain of multiple schemas, but only of
Expand All @@ -33,6 +32,7 @@
* @see org.apache.olingo.client.api.data.ServiceDocument
*/
public class IntermediateServiceDocument {
private final static String DYNAMICTYPE_NAMESPACE = "odata.dynamic";
private final Object lock = new Object();
private final Map<String, AbstractJPASchema> schemaListInternalKey = new HashMap<>();
private boolean dependendSchemaCreationRequired = false;
Expand Down Expand Up @@ -208,8 +208,8 @@ public JPAStructuredType<?> getStructuredType(final FullQualifiedName typeName)
return null;
}

JPAEntityType<?> getEntityType(final Class<?> targetClass) {
JPAEntityType<?> entityType;
<X> JPAEntityType<X> getEntityType(final Class<X> targetClass) {
JPAEntityType<X> entityType;
if (Object.class.equals(targetClass)) {
return null;
}
Expand Down Expand Up @@ -239,8 +239,8 @@ IntermediateEnumType getEnumType(final Class<?> targetClass) {
return null;
}

JPAStructuredType<?> getComplexType(final Class<?> targetClass) {
JPAStructuredType<?> complexType;
<X> JPAStructuredType<X> getComplexType(final Class<X> targetClass) {
JPAStructuredType<X> complexType;
if (Object.class.equals(targetClass)) {
return null;
}
Expand Down Expand Up @@ -293,15 +293,13 @@ IntermediateEnumType findOrCreateEnumType(final Class<? extends Enum<?>> clazz)
*
* @see IntermediateCustomSchema#createDynamicMapType(Class, Class, boolean)
*/
IntermediateMapComplexTypeDTO createDynamicJavaUtilMapType(final Class<?> mapKeyType,
final Class<?> mapValueType, final boolean valueIsCollection) throws ODataJPAModelException {
final String namespace = Map.class.getPackage().getName();
final AbstractJPASchema schema = findOrCreateCustomSchema(namespace);
<X, Y> IntermediateMapComplexTypeDTO<Y> createDynamicJavaUtilMapType(final Class<Y> mapValueType, final boolean valueIsCollection) throws ODataJPAModelException {
final AbstractJPASchema schema = findOrCreateCustomSchema(DYNAMICTYPE_NAMESPACE);
// Map type is created on-demand while creating other DTO types, so we have to avoid to reset the container
return ((IntermediateCustomSchema) schema).createDynamicMapType(mapKeyType, mapValueType, valueIsCollection);
return ((IntermediateCustomSchema) schema).createDynamicMapType(mapValueType, valueIsCollection);
}

AbstractIntermediateComplexTypeDTO<?> findOrCreateDTOComplexType(final Class<?> clazz) throws ODataJPAModelException {
<T> AbstractIntermediateComplexTypeDTO<T> findOrCreateDTOComplexType(final Class<T> clazz) throws ODataJPAModelException {
// the same class could be register as @Embeddable via JPA in another namespace... we accept that currently
synchronized (lock) {
final String namespace = clazz.getPackage().getName();
Expand All @@ -310,7 +308,7 @@ AbstractIntermediateComplexTypeDTO<?> findOrCreateDTOComplexType(final Class<?>
}
}

public IntermediateEnityTypeDTO<?> createDTOType(final Class<?> clazz) throws ODataJPAModelException {
public <T> IntermediateEnityTypeDTO<T> createDTOEntityType(final Class<T> clazz) throws ODataJPAModelException {
synchronized (lock) {
if (clazz == null) {
throw new ODataJPAModelException(MessageKeys.GENERAL);
Expand All @@ -321,13 +319,38 @@ public IntermediateEnityTypeDTO<?> createDTOType(final Class<?> clazz) throws OD
// DTO's can be defined only in custom schemas
throw new ODataJPAModelException(MessageKeys.RUNTIME_PROBLEM);
}
if(((IntermediateCustomSchema) schema).getEntityType(clazz) != null) {
//already existing
throw new ODataJPAModelException(MessageKeys.GENERAL);
}
// this will affect the number of entity set's so we have to refresh the container
intermediateContainer.reset();
return ((IntermediateCustomSchema) schema).findOrCreateDTOType(clazz);
return ((IntermediateCustomSchema) schema).findOrCreateDTOEntityType(clazz);
}
}

public JPAElement getEntitySet(final JPAEntityType<?> entityType) throws ODataJPAModelException {
public <T> AbstractIntermediateComplexTypeDTO<T> createDTOComplexType(final Class<T> clazz) throws ODataJPAModelException {
synchronized (lock) {
if (clazz == null) {
throw new ODataJPAModelException(MessageKeys.GENERAL);
}
final String namespace = clazz.getPackage().getName();
final AbstractJPASchema schema = findOrCreateCustomSchema(namespace);
if (!IntermediateCustomSchema.class.isInstance(schema)) {
// DTO's can be defined only in custom schemas
throw new ODataJPAModelException(MessageKeys.RUNTIME_PROBLEM);
}
if(((IntermediateCustomSchema) schema).getComplexType(clazz) != null) {
//already existing
throw new ODataJPAModelException(MessageKeys.GENERAL);
}
// this will affect the number of entity set's so we have to refresh the container
intermediateContainer.reset();
return ((IntermediateCustomSchema) schema).findOrCreateDTOComplexType(clazz);
}
}

public JPAEntitySet getEntitySet(final JPAEntityType<?> entityType) throws ODataJPAModelException {
synchronized (lock) {
resolveSchemas();
for (final AbstractJPASchema schema : schemaListInternalKey.values()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ public JPAODataGlobalContextImpl(final JPAAdapter mappingAdapter) throws ODataEx
di.registerDependencyMapping(JPAEdmProvider.class, jpaEdm);
di.registerDependencyMapping(JPAODataGlobalContext.class, this);

registerDTOs();
registerDTOEntityTypes();
registerDTOComplexTypes();
}

void dispose() {
Expand All @@ -61,18 +62,30 @@ ServerCoreDebugger getServerDebugger() {
return serverDebugger;
}

private void registerDTOs() throws ODataJPAModelException {
final Collection<Class<?>> dtos = mappingAdapter.getDTOs();
private void registerDTOEntityTypes() throws ODataJPAModelException {
final Collection<Class<?>> dtos = mappingAdapter.getDTOEntityTypes();
if (dtos == null || dtos.isEmpty()) {
return;
}

final IntermediateServiceDocument sd = jpaEdm.getServiceDocument();
for (final Class<?> dtoClass : dtos) {
sd.createDTOType(dtoClass);
sd.createDTOEntityType(dtoClass);
}
}

private void registerDTOComplexTypes() throws ODataJPAModelException {
final Collection<Class<?>> cts = mappingAdapter.getDTOComplexTypes();
if (cts == null || cts.isEmpty()) {
return;
}

final IntermediateServiceDocument sd = jpaEdm.getServiceDocument();
for (final Class<?> ctClass : cts) {
sd.createDTOComplexType(ctClass);
}
}

@Override
public OData getOdata() {
if (disposed) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public interface QueryResponseCustomizer {
* values. Entities (and also DTO's) cannot be used for dynamic properties. A complex type can be every
* JPA @Embeddable or every other class annotated with
* {@link org.apache.olingo.jpa.metadata.core.edm.complextype.ODataComplexType @ODataComplexType} and known to meta
* model via DTO registration in {@link org.apache.olingo.jpa.processor.core.mapping.JPAAdapter#getDTOs()
* model via registration in {@link org.apache.olingo.jpa.processor.core.mapping.JPAAdapter#getDTOComplexTypes()
* JPAAdapter}. The complex type cannot have more dynamic properties, because it not markable as open type.</li>
* <li>Add additional entities to collection: This should not happen as replacement for usage of
* {@link org.apache.olingo.jpa.metadata.core.edm.dto.ODataDTO DTO}'s.</li>
Expand Down
Loading

0 comments on commit 81b9094

Please sign in to comment.