- *
+ * no group, or in other words that are not annotated, get selected. For properties that belong to another group are
+ * requested, a null value is returned.
+ *
+ * This method does not nothing and returns false if the ETag value is
+ * null.
+ *
+ * @param eTag the ETag value to match
+ * @param ifMatchHeaders the If-Match header values
+ * @param ifNoneMatchHeaders the If-None-Match header values
+ * @return whether a "Not Modified" response should be used
+ */
+ public boolean checkReadPreconditions(String etag,
+ Collection ifMatchHeaders, Collection ifNoneMatchHeaders)
+ throws PreconditionException;
+
+ /**
+ *
+ * Checks the preconditions of a change request (with HTTP methods PUT, PATCH, or DELETE)
+ * with a given ETag value against the If-Match and If-None-Match HTTP headers.
+ *
+ *
+ * If the given ETag value is not matched by the ETag information in the If-Match headers,
+ * and there are ETags in the headers to be matched, or
+ * if the given ETag value is matched by the ETag information in the If-None-Match headers,
+ * a "Precondition Failed" exception is thrown.
+ *
+ *
+ * All matching uses weak comparison as described in
+ * RFC 7232, section 2.3.2.
+ *
+ *
+ * This method does not nothing if the ETag value is null.
+ *
+ * @param eTag the ETag value to match
+ * @param ifMatchHeaders the If-Match header values
+ * @param ifNoneMatchHeaders the If-None-Match header values
+ */
+ public void checkChangePreconditions(String etag,
+ Collection ifMatchHeaders, Collection ifNoneMatchHeaders)
+ throws PreconditionException;
+
+ /**
+ * Converts the value of an ETag into a the corresponding string.
+ * A value is converted by calling the toString method with one exception. In case
+ * the value is an instance of type {@link java.sql.Timestamp}, the time stamp is first converted into
+ * a {@link java.time.Instant} to get a RFC 7232 compliant string.
+ *
+ * @param entityType the ETag belongs to
+ * @param value raw value of the ETag
+ * @return ETag string. It is empty if the value is null and null if the entity type has no ETag property
+ * @throws ODataJPAQueryException
+ */
+ public String asEtag(@Nonnull final JPAEntityType entityType, final Object value) throws ODataJPAQueryException;
+}
diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataRequestContextAccess.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataRequestContextAccess.java
index f190bb19e..a5391e9a1 100644
--- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataRequestContextAccess.java
+++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataRequestContextAccess.java
@@ -71,4 +71,6 @@ public Optional getQueryEnhancement(@Nonnull final JP
public List getProvidedLocale();
public JPAODataQueryDirectives getQueryDirectives();
+
+ public JPAODataEtagHelper getEtagHelper();
}
diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataRequestHandler.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataRequestHandler.java
index b84f0e336..9006533c2 100644
--- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataRequestHandler.java
+++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataRequestHandler.java
@@ -53,7 +53,7 @@ public JPAODataRequestHandler(final JPAODataSessionContextAccess serviceContext,
final OData odata) {
this.emf = serviceContext.getEntityManagerFactory();
this.serviceContext = (JPAODataServiceContext) serviceContext;
- this.requestContext = new JPAODataInternalRequestContext(requestContext, serviceContext);
+ this.requestContext = new JPAODataInternalRequestContext(requestContext, serviceContext, odata);
this.odata = odata;
}
diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataTransactionFactory.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataTransactionFactory.java
index 28b791cc0..bc4ce5266 100644
--- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataTransactionFactory.java
+++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataTransactionFactory.java
@@ -5,8 +5,6 @@
import com.sap.olingo.jpa.processor.core.exception.ODataJPATransactionException;
/**
- * A wrapper to abstract from various transaction APIs provided by JAVA or e.g. Spring like
- * jakarta.persistence.EntityTransaction, javax.transaction.UserTransaction, javax.transaction.Transaction or
* A wrapper to abstract from various transaction APIs provided by JAVA or e.g. Spring like
* javax.persistence.EntityTransaction, javax.transaction.UserTransaction, javax.transaction.Transaction or
* org.springframework.transaction.jta.JtaTransactionManager.
diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/example/JPAExamplePagingProvider.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/example/JPAExamplePagingProvider.java
index 3c1ebd87b..3a0df6da4 100644
--- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/example/JPAExamplePagingProvider.java
+++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/example/JPAExamplePagingProvider.java
@@ -128,5 +128,4 @@ private void addToCache(final JPAODataPage page, final Long count) {
}
private static record CacheEntry(Long maxTop, JPAODataPage page) {}
-
}
diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/converter/JPAEntityResultConverter.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/converter/JPAEntityResultConverter.java
index c80d48b73..03711e22b 100644
--- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/converter/JPAEntityResultConverter.java
+++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/converter/JPAEntityResultConverter.java
@@ -1,29 +1,42 @@
package com.sap.olingo.jpa.processor.core.converter;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
+import java.util.Map;
+
+import javax.annotation.CheckForNull;
import org.apache.olingo.commons.api.data.Entity;
import org.apache.olingo.commons.api.data.EntityCollection;
import org.apache.olingo.commons.api.data.Property;
import org.apache.olingo.commons.api.edm.EdmEntityType;
+import org.apache.olingo.commons.api.http.HttpStatusCode;
import org.apache.olingo.server.api.ODataApplicationException;
import org.apache.olingo.server.api.serializer.SerializerException;
import org.apache.olingo.server.api.uri.UriHelper;
+import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAElement;
+import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEntityType;
import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAServiceDocument;
+import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAStructuredType;
import com.sap.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelException;
+import com.sap.olingo.jpa.processor.core.api.JPAODataEtagHelper;
+import com.sap.olingo.jpa.processor.core.exception.ODataJPAQueryException;
public class JPAEntityResultConverter extends JPAStructuredResultConverter {
private final EdmEntityType edmEntityType;
private final UriHelper odataUriHelper;
+ private final JPAODataEtagHelper etagHelper;
public JPAEntityResultConverter(final UriHelper uriHelper, final JPAServiceDocument sd, final List> jpaQueryResult,
- final EdmEntityType returnType) throws ODataJPAModelException {
+ final EdmEntityType returnType, final JPAODataEtagHelper etagHelper) throws ODataJPAModelException {
super(jpaQueryResult, sd.getEntity(returnType));
this.edmEntityType = returnType;
this.odataUriHelper = uriHelper;
+ this.etagHelper = etagHelper;
}
@Override
@@ -36,10 +49,32 @@ public EntityCollection getResult() throws ODataApplicationException, Serializer
odataEntity.setType(this.jpaTopLevelType.getExternalFQN().getFullQualifiedNameAsString());
final List properties = odataEntity.getProperties();
convertProperties(row, properties, jpaTopLevelType);
+ odataEntity.setETag(createEtag(row, jpaTopLevelType));
odataEntity.setId(new URI(odataUriHelper.buildKeyPredicate(edmEntityType, odataEntity)));
odataResults.add(odataEntity);
}
return odataEntityCollection;
}
+ @CheckForNull
+ private String createEtag(final Object row, final JPAStructuredType jpaType) throws ODataJPAQueryException {
+ if (jpaType instanceof final JPAEntityType et) {
+ try {
+ if (et.hasEtag()) {
+ Object value = row;
+ for (final JPAElement part : et.getEtagPath().getPath()) {
+ final Map methodMap = getMethods(value.getClass());
+ final Method getMethod = getGetter(part.getInternalName(), methodMap);
+ value = getMethod.invoke(value);
+ }
+ return etagHelper.asEtag(et, value);
+ }
+ } catch (final ODataJPAModelException | IllegalAccessException | IllegalArgumentException
+ | InvocationTargetException e) {
+ throw new ODataJPAQueryException(e, HttpStatusCode.INTERNAL_SERVER_ERROR);
+ }
+ }
+ return null;
+
+ }
}
diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/converter/JPAStructuredResultConverter.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/converter/JPAStructuredResultConverter.java
index 8cfc20a29..5505443e8 100644
--- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/converter/JPAStructuredResultConverter.java
+++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/converter/JPAStructuredResultConverter.java
@@ -124,14 +124,18 @@ private void convertPrimitiveProperty(final Object row, final List properties, final JPAAttribute attribute,
final Method getMethod) throws ODataJPAModelException, ODataJPAQueryException, IllegalAccessException,
InvocationTargetException {
- final ComplexValue complexValue = new ComplexValue();
- properties.add(new Property(
- attribute.getStructuredType().getExternalFQN().getFullQualifiedNameAsString(),
- attribute.getExternalName(),
- ValueType.COMPLEX,
- complexValue));
- final List values = complexValue.getValue();
- convertProperties(getMethod.invoke(row), values, attribute.getStructuredType());
+
+ final var complexResult = getMethod.invoke(row);
+ if (complexResult != null) {
+ final ComplexValue complexValue = new ComplexValue();
+ properties.add(new Property(
+ attribute.getStructuredType().getExternalFQN().getFullQualifiedNameAsString(),
+ attribute.getExternalName(),
+ ValueType.COMPLEX,
+ complexValue));
+ final List values = complexValue.getValue();
+ convertProperties(complexResult, values, attribute.getStructuredType());
+ }
}
private void convertCollectionProperty(final Object row, final List properties,
@@ -155,7 +159,7 @@ private void convertCollectionProperty(final Object row, final List pr
collection));
}
- private Method getGetter(final String attributeName, final Map methodMap)
+ Method getGetter(final String attributeName, final Map methodMap)
throws ODataJPAQueryException {
final String getterName = ACCESS_MODIFIER_GET + JPADefaultEdmNameBuilder.firstToUpper(attributeName);
diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/converter/JPATupleChildConverter.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/converter/JPATupleChildConverter.java
index 72f45305a..1279c79bf 100644
--- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/converter/JPATupleChildConverter.java
+++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/converter/JPATupleChildConverter.java
@@ -251,7 +251,7 @@ private void createEtag(@Nonnull final JPAEntityType rowEntity, final Tuple row,
final String etagAlias = rowEntity.getEtagPath().getAlias();
final Object etag = row.get(etagAlias);
if (etag != null) {
- odataEntity.setETag(etag.toString());
+ odataEntity.setETag(requestContext.getEtagHelper().asEtag(rowEntity, etag));
}
}
diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/converter/JPATupleResultConverter.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/converter/JPATupleResultConverter.java
index 6a134a447..0a742771c 100644
--- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/converter/JPATupleResultConverter.java
+++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/converter/JPATupleResultConverter.java
@@ -250,11 +250,11 @@ void convertPrimitiveAttribute(final Object
throw new ODataJPAProcessorException(e, HttpStatusCode.INTERNAL_SERVER_ERROR);
}
}
+ } else if (attribute != null && value != null && attribute.isEnum() && !value.getClass().isArray()) {
+ odataValue = ((Enum>) value).ordinal();
} else if (attribute != null && attribute.getConverter() != null) {
final AttributeConverter converter = attribute.getConverter();
odataValue = converter.convertToDatabaseColumn((T) value);
- } else if (attribute != null && value != null && attribute.isEnum()) {
- odataValue = ((Enum>) value).ordinal();
} else if (attribute != null && value != null && attribute.isCollection()) {
return;
} else if (attribute != null && value != null && attribute.getType() == Duration.class) {
diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPADefaultDatabaseProcessor.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPADefaultDatabaseProcessor.java
index 259aaeb5f..62ffe3163 100644
--- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPADefaultDatabaseProcessor.java
+++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPADefaultDatabaseProcessor.java
@@ -18,6 +18,8 @@
import org.apache.olingo.server.api.uri.queryoption.SearchOption;
import org.apache.olingo.server.api.uri.queryoption.expression.BinaryOperatorKind;
+import com.sap.olingo.jpa.metadata.api.JPAHttpHeaderMap;
+import com.sap.olingo.jpa.metadata.api.JPARequestParameterMap;
import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPADataBaseFunction;
import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEntityType;
import com.sap.olingo.jpa.processor.core.exception.ODataJPADBAdaptorException;
@@ -101,14 +103,15 @@ public Expression createSearchWhereClause(final CriteriaBuilder cb, fin
@SuppressWarnings("unchecked")
@Override
- public List executeFunctionQuery(final List uriResourceParts,
- final JPADataBaseFunction jpaFunction, final EntityManager em) throws ODataApplicationException {
+ public Object executeFunctionQuery(final List uriResourceParts,
+ final JPADataBaseFunction jpaFunction, final EntityManager em, final JPAHttpHeaderMap headers,
+ final JPARequestParameterMap parameters) throws ODataApplicationException {
final UriResource last = uriResourceParts.get(uriResourceParts.size() - 1);
if (last.getKind() == UriResourceKind.count) {
final List countResult = new ArrayList<>();
countResult.add(executeCountQuery(uriResourceParts, jpaFunction, em, SELECT_COUNT_PATTERN));
- return (List) countResult;
+ return countResult;
}
if (last.getKind() == UriResourceKind.function)
return executeQuery(uriResourceParts, jpaFunction, em, SELECT_BASE_PATTERN);
diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPAODataDatabaseTableFunction.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPAODataDatabaseTableFunction.java
index 8c6fbf874..d72a910bd 100644
--- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPAODataDatabaseTableFunction.java
+++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPAODataDatabaseTableFunction.java
@@ -1,5 +1,6 @@
package com.sap.olingo.jpa.processor.core.database;
+import java.util.Collections;
import java.util.List;
import jakarta.persistence.EntityManager;
@@ -7,10 +8,46 @@
import org.apache.olingo.server.api.ODataApplicationException;
import org.apache.olingo.server.api.uri.UriResource;
+import com.sap.olingo.jpa.metadata.api.JPAHttpHeaderMap;
+import com.sap.olingo.jpa.metadata.api.JPARequestParameterMap;
import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPADataBaseFunction;
public interface JPAODataDatabaseTableFunction {
- List executeFunctionQuery(final List uriResourceParts, final JPADataBaseFunction jpaFunction,
- final EntityManager em) throws ODataApplicationException;
+ /**
+ *
+ * @param
+ * @param uriResourceParts
+ * @param jpaFunction
+ * @param em
+ * @return
+ * @throws ODataApplicationException
+ *
+ * @deprecated implement
+ * {@link #executeFunctionQuery(List, JPADataBaseFunction, EntityManager, JPAHttpHeaderMap, JPARequestParameterMap)}
+ * instead
+ */
+ @Deprecated(since = "2.1.2", forRemoval = true)
+ default List executeFunctionQuery(final List uriResourceParts,
+ final JPADataBaseFunction jpaFunction, final EntityManager em) throws ODataApplicationException {
+ return Collections.emptyList();
+ }
+
+ /**
+ *
+ * @param As of now only {@link java.util.List} is supported
+ * @param uriResourceParts
+ * @param jpaFunction
+ * @param em
+ * @param headers
+ * @param parameters
+ * @return A primitive type, a complex type, an entity type or a list of one of those. According to the defined return
+ * type
+ * @throws ODataApplicationException
+ */
+ default Object executeFunctionQuery(final List uriResourceParts,
+ final JPADataBaseFunction jpaFunction, final EntityManager em, final JPAHttpHeaderMap headers,
+ final JPARequestParameterMap parameters) throws ODataApplicationException {
+ return executeFunctionQuery(uriResourceParts, jpaFunction, em);
+ }
}
diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPA_DERBY_DatabaseProcessor.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPA_DERBY_DatabaseProcessor.java
index 4ebc1e1e0..4111fed76 100644
--- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPA_DERBY_DatabaseProcessor.java
+++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPA_DERBY_DatabaseProcessor.java
@@ -17,6 +17,8 @@
import org.apache.olingo.server.api.uri.UriResourceKind;
import org.apache.olingo.server.api.uri.queryoption.SearchOption;
+import com.sap.olingo.jpa.metadata.api.JPAHttpHeaderMap;
+import com.sap.olingo.jpa.metadata.api.JPARequestParameterMap;
import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPADataBaseFunction;
import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEntityType;
import com.sap.olingo.jpa.processor.core.exception.ODataJPADBAdaptorException;
@@ -37,17 +39,17 @@ public Expression createSearchWhereClause(final CriteriaBuilder cb, fin
/**
* See: Derby: Function Invocation
*/
- @SuppressWarnings("unchecked")
@Override
- public java.util.List executeFunctionQuery(final List uriResourceParts,
- final JPADataBaseFunction jpaFunction, final EntityManager em) throws ODataApplicationException {
+ public Object executeFunctionQuery(final List uriResourceParts,
+ final JPADataBaseFunction jpaFunction, final EntityManager em, final JPAHttpHeaderMap headers,
+ final JPARequestParameterMap parameters) throws ODataApplicationException {
final UriResource last = uriResourceParts.get(uriResourceParts.size() - 1);
if (last.getKind() == UriResourceKind.count) {
final List countResult = new ArrayList<>();
countResult.add(executeCountQuery(uriResourceParts, jpaFunction, em, SELECT_COUNT_PATTERN));
- return (List) countResult;
+ return countResult;
}
if (last.getKind() == UriResourceKind.function)
return executeQuery(uriResourceParts, jpaFunction, em, SELECT_BASE_PATTERN);
diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPA_HSQLDB_DatabaseProcessor.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPA_HSQLDB_DatabaseProcessor.java
index 84b5358f9..ef2d5e39e 100644
--- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPA_HSQLDB_DatabaseProcessor.java
+++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPA_HSQLDB_DatabaseProcessor.java
@@ -17,6 +17,8 @@
import org.apache.olingo.server.api.uri.UriResourceKind;
import org.apache.olingo.server.api.uri.queryoption.SearchOption;
+import com.sap.olingo.jpa.metadata.api.JPAHttpHeaderMap;
+import com.sap.olingo.jpa.metadata.api.JPARequestParameterMap;
import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPADataBaseFunction;
import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEntityType;
import com.sap.olingo.jpa.processor.core.exception.ODataJPADBAdaptorException;
@@ -35,16 +37,16 @@ public Expression createSearchWhereClause(final CriteriaBuilder cb, fin
HttpStatusCode.NOT_IMPLEMENTED);
}
- @SuppressWarnings("unchecked")
@Override
- public List executeFunctionQuery(final List uriResourceParts,
- final JPADataBaseFunction jpaFunction, final EntityManager em) throws ODataApplicationException {
+ public Object executeFunctionQuery(final List uriResourceParts,
+ final JPADataBaseFunction jpaFunction, final EntityManager em, final JPAHttpHeaderMap headers,
+ final JPARequestParameterMap parameters) throws ODataApplicationException {
final UriResource last = uriResourceParts.get(uriResourceParts.size() - 1);
if (last.getKind() == UriResourceKind.count) {
final List countResult = new ArrayList<>();
countResult.add(executeCountQuery(uriResourceParts, jpaFunction, em, SELECT_COUNT_PATTERN));
- return (List) countResult;
+ return countResult;
}
if (last.getKind() == UriResourceKind.function)
return executeQuery(uriResourceParts, jpaFunction, em, SELECT_BASE_PATTERN);
diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPA_POSTSQL_DatabaseProcessor.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPA_POSTSQL_DatabaseProcessor.java
index d0ccede8b..5dadc6cbb 100644
--- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPA_POSTSQL_DatabaseProcessor.java
+++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPA_POSTSQL_DatabaseProcessor.java
@@ -17,6 +17,8 @@
import org.apache.olingo.server.api.uri.UriResourceKind;
import org.apache.olingo.server.api.uri.queryoption.SearchOption;
+import com.sap.olingo.jpa.metadata.api.JPAHttpHeaderMap;
+import com.sap.olingo.jpa.metadata.api.JPARequestParameterMap;
import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPADataBaseFunction;
import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEntityType;
import com.sap.olingo.jpa.processor.core.exception.ODataJPADBAdaptorException;
@@ -45,17 +47,17 @@ public Expression createSearchWhereClause(final CriteriaBuilder cb, fin
HttpStatusCode.NOT_IMPLEMENTED);
}
- @SuppressWarnings("unchecked")
@Override
- public List executeFunctionQuery(final List uriResourceParts,
- final JPADataBaseFunction jpaFunction, final EntityManager em) throws ODataApplicationException {
+ public Object executeFunctionQuery(final List uriResourceParts,
+ final JPADataBaseFunction jpaFunction, final EntityManager em, final JPAHttpHeaderMap headers,
+ final JPARequestParameterMap parameters) throws ODataApplicationException {
final UriResource last = uriResourceParts.get(uriResourceParts.size() - 1);
if (last.getKind() == UriResourceKind.count) {
final List countResult = new ArrayList<>();
countResult.add(executeCountQuery(uriResourceParts, jpaFunction, em, SELECT_COUNT_PATTERN));
- return (List) countResult;
+ return countResult;
}
if (last.getKind() == UriResourceKind.function)
return executeQuery(uriResourceParts, jpaFunction, em, SELECT_BASE_PATTERN);
diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/exception/ODataJPAProcessorException.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/exception/ODataJPAProcessorException.java
index e87ef7b58..1f74e1089 100644
--- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/exception/ODataJPAProcessorException.java
+++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/exception/ODataJPAProcessorException.java
@@ -51,6 +51,7 @@ public enum MessageKeys implements ODataJPAMessageKey {
EXPAND_NON_SUPPORTED_AT_ALL,
EXPAND_EXCEEDS_MAX_LEVEL,
+ VALIDATION_NOT_POSSIBLE_TOO_MANY_RESULTS,
COUNT_NON_SUPPORTED_COUNT;
@Override
@@ -85,7 +86,8 @@ public ODataJPAProcessorException(final MessageKeys messageKey, final HttpStatus
super(messageKey.getKey(), statusCode, params);
}
- public ODataJPAProcessorException(final MessageKeys messageKey, final HttpStatusCode statusCode, final Throwable exception) {
+ public ODataJPAProcessorException(final MessageKeys messageKey, final HttpStatusCode statusCode,
+ final Throwable exception) {
super(messageKey.getKey(), statusCode, exception);
}
diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/exception/ODataJPAQueryException.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/exception/ODataJPAQueryException.java
index 3e61a4530..b04c67845 100644
--- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/exception/ODataJPAQueryException.java
+++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/exception/ODataJPAQueryException.java
@@ -37,6 +37,7 @@ public enum MessageKeys implements ODataJPAMessageKey {
QUERY_PREPARATION_ORDER_BY_NOT_SUPPORTED,
QUERY_PREPARATION_JOIN_TABLE_TYPE_MISSING,
QUERY_PREPARATION_COLLECTION_PROPERTY_NOT_SUPPORTED,
+ QUERY_PREPARATION_NO_PAGE_FOUND,
NOT_SUPPORTED_RESOURCE_TYPE,
MISSING_CLAIMS_PROVIDER,
MISSING_CLAIM,
diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/filter/JPAExistsOperation.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/filter/JPAExistsOperation.java
index c23a4de31..f092c4e65 100644
--- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/filter/JPAExistsOperation.java
+++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/filter/JPAExistsOperation.java
@@ -16,6 +16,7 @@
import org.apache.olingo.server.api.uri.UriResource;
import org.apache.olingo.server.api.uri.UriResourceComplexProperty;
import org.apache.olingo.server.api.uri.UriResourceEntitySet;
+import org.apache.olingo.server.api.uri.UriResourceLambdaVariable;
import org.apache.olingo.server.api.uri.UriResourceNavigation;
import org.apache.olingo.server.api.uri.UriResourcePartTyped;
import org.apache.olingo.server.api.uri.UriResourceProperty;
@@ -71,30 +72,30 @@ protected List determineAssociations(final JPAService
StringBuilder associationName = null;
UriResourcePartTyped navigation = null;
- if (resourceParts != null && (Utility.hasNavigation(resourceParts) || hasCollection(resourceParts))) {
+ if (Utility.hasNavigation(resourceParts) || Utility.hasCollection(resourceParts)) {
for (int i = resourceParts.size() - 1; i >= 0; i--) {
final UriResource resourcePart = resourceParts.get(i);
- if (resourcePart instanceof UriResourceNavigation) {
+ if (resourcePart instanceof final UriResourceNavigation nextNavigation) {
if (navigation != null)
pathList.add(new JPANavigationPropertyInfo(sd, navigation, Utility.determineAssociationPath(sd,
- ((UriResourcePartTyped) resourceParts.get(i)), associationName), null));
- navigation = (UriResourceNavigation) resourceParts.get(i);
+ nextNavigation, associationName), null));
+ navigation = nextNavigation;
associationName = new StringBuilder();
- associationName.insert(0, ((UriResourceNavigation) navigation).getProperty().getName());
+ associationName.insert(0, nextNavigation.getProperty().getName());
}
if (navigation != null) {
if (resourceParts.get(i) instanceof final UriResourceComplexProperty complexProperty) {
associationName.insert(0, JPAPath.PATH_SEPARATOR);
associationName.insert(0, complexProperty.getProperty().getName());
- }
- if (resourcePart instanceof UriResourceEntitySet)
+ } else if (resourcePart instanceof final UriResourceEntitySet entitySet)
pathList.add(new JPANavigationPropertyInfo(sd, navigation, Utility.determineAssociationPath(sd,
- ((UriResourcePartTyped) resourceParts.get(i)), associationName), null));
+ entitySet, associationName), null));
+ else if (resourcePart instanceof final UriResourceLambdaVariable lambdaVariable)
+ pathList.add(new JPANavigationPropertyInfo(sd, navigation, Utility.determineAssociation(sd,
+ lambdaVariable.getType(), associationName), null));
}
- /* placing this condition at the end makes sure that other conditions are not entered for the resource
- that's being processed once navigation object gets populated */
- if (isCollection(resourcePart)) {
- navigation = (UriResourcePartTyped) resourceParts.get(i);
+ if (Utility.isCollection(resourcePart)) {
+ navigation = (UriResourcePartTyped) resourcePart;
associationName = new StringBuilder();
associationName.insert(0, ((UriResourceProperty) navigation).getProperty().getName());
}
@@ -103,20 +104,5 @@ protected List determineAssociations(final JPAService
return pathList;
}
- public boolean hasCollection(final List resourceParts) {
- if (resourceParts != null) {
- for (int i = resourceParts.size() - 1; i >= 0; i--) {
- if (isCollection(resourceParts.get(i)))
- return true;
- }
- }
- return false;
- }
-
- public boolean isCollection(final UriResource resourcePart) {
-
- return (resourcePart instanceof final UriResourceProperty resourceProperty && resourceProperty.isCollection());
- }
-
- protected static record SubQueryItem(List>> jpaPath, Subquery>> query) {}
+ protected record SubQueryItem(List>> jpaPath, Subquery>> query) {}
}
\ No newline at end of file
diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/filter/JPALambdaOperation.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/filter/JPALambdaOperation.java
index 6a52a2661..49b402e62 100644
--- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/filter/JPALambdaOperation.java
+++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/filter/JPALambdaOperation.java
@@ -25,8 +25,8 @@ abstract class JPALambdaOperation extends JPAExistsOperation {
protected final UriInfoResource member;
- JPALambdaOperation(final JPAFilterComplierAccess jpaComplier, final Member member) {
- super(jpaComplier);
+ JPALambdaOperation(final JPAFilterComplierAccess jpaCompiler, final Member member) {
+ super(jpaCompiler);
this.member = member.getResourcePath();
}
@@ -38,7 +38,11 @@ protected SubQueryItem getExistsQuery() throws ODataApplicationException {
@SuppressWarnings("unchecked")
protected final Subquery getSubQuery(final Expression expression)
throws ODataApplicationException {
- final List allUriResourceParts = new ArrayList<>(uriResourceParts);
+ // Add association root, which is only available for the first lambda expression
+ final List allUriResourceParts = new ArrayList<>();
+ if (uriResourceParts != null)
+ allUriResourceParts.addAll(uriResourceParts);
+ // Add association path
allUriResourceParts.addAll(member.getUriResourceParts());
// 1. Determine all relevant associations
diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAAbstractRequestProcessor.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAAbstractRequestProcessor.java
index 0704a3b98..1bc16cebc 100644
--- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAAbstractRequestProcessor.java
+++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAAbstractRequestProcessor.java
@@ -1,8 +1,11 @@
package com.sap.olingo.jpa.processor.core.processor;
+import javax.annotation.Nullable;
+
import jakarta.persistence.EntityManager;
import jakarta.persistence.criteria.CriteriaBuilder;
+import org.apache.olingo.commons.api.data.EntityCollection;
import org.apache.olingo.commons.api.ex.ODataException;
import org.apache.olingo.commons.api.format.ContentType;
import org.apache.olingo.commons.api.http.HttpHeader;
@@ -43,10 +46,29 @@ abstract class JPAAbstractRequestProcessor {
}
protected final void createSuccessResponse(final ODataResponse response, final ContentType responseFormat,
- final SerializerResult serializerResult) {
+ final SerializerResult serializerResult, @Nullable final EntityCollection entityCollection) {
response.setContent(serializerResult.getContent());
response.setStatusCode(successStatusCode);
response.setHeader(HttpHeader.CONTENT_TYPE, responseFormat.toContentTypeString());
+ createETagHeader(response, entityCollection);
+ }
+
+ protected final void createNotModifiedResponse(final ODataResponse response,
+ final EntityCollection entityCollection) {
+ response.setStatusCode(HttpStatusCode.NOT_MODIFIED.getStatusCode());
+ createETagHeader(response, entityCollection);
+ }
+
+ protected final void createPreconditionFailedResponse(final ODataResponse response) {
+ response.setStatusCode(HttpStatusCode.PRECONDITION_FAILED.getStatusCode());
+ }
+
+ private void createETagHeader(final ODataResponse response, final EntityCollection entityCollection) {
+ if (entityCollection != null && entityCollection.getEntities().size() == 1) {
+ final var etag = entityCollection.getEntities().get(0).getETag();
+ if (etag != null)
+ response.setHeader(HttpHeader.ETAG, etag);
+ }
}
}
\ No newline at end of file
diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAActionRequestProcessor.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAActionRequestProcessor.java
index 410184a64..3b3010457 100644
--- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAActionRequestProcessor.java
+++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAActionRequestProcessor.java
@@ -61,7 +61,8 @@ public void performAction(final ODataRequest request, final ODataResponse respon
final JPAAction jpaAction = sd.getAction(resource.getAction());
if (jpaAction == null)
throw new ODataJPAProcessorException(ACTION_UNKNOWN, BAD_REQUEST, resource.getAction().getName());
- final Object instance = createInstance(em, jpaAction);
+ final Object instance = createInstance(jpaAction, em, requestContext.getHeader(), requestContext
+ .getRequestParameter());
final ODataDeserializer deserializer = odata.createDeserializer(requestFormat);
final Map actionParameter =
diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPACUDRequestProcessor.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPACUDRequestProcessor.java
index 738d15148..e6b94a67d 100644
--- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPACUDRequestProcessor.java
+++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPACUDRequestProcessor.java
@@ -497,7 +497,7 @@ private void createCreateResponse(final ODataRequest request, final ODataRespons
final Entity createdEntity = convertEntity(et, result, request.getAllHeaders());
final EntityCollection entities = new EntityCollection();
entities.getEntities().add(createdEntity);
- createSuccessResponse(response, responseFormat, serializer.serialize(request, entities));
+ createSuccessResponse(response, responseFormat, serializer.serialize(request, entities), entities);
response.setHeader(HttpHeader.LOCATION, location);
}
}
@@ -688,7 +688,7 @@ private void createUpdateResponse(final ODataRequest request, final ODataRespons
}
final EntityCollection entities = new EntityCollection();
entities.getEntities().add(updatedEntity);
- createSuccessResponse(response, responseFormat, serializer.serialize(request, entities));
+ createSuccessResponse(response, responseFormat, serializer.serialize(request, entities), entities);
}
}
diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPACoreDebugger.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPACoreDebugger.java
index 140e4cb6c..d0e7138f8 100644
--- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPACoreDebugger.java
+++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPACoreDebugger.java
@@ -137,6 +137,7 @@ long getCurrentThreadMemoryConsumption() {
}
private long getMemoryConsumption() {
+
final ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
if (threadMXBean instanceof final com.sun.management.ThreadMXBean sunMXBean) {
return sunMXBean.getThreadAllocatedBytes(Thread.currentThread().getId());
diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPACountRequestProcessor.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPACountRequestProcessor.java
index c9e748cf2..bada88e1e 100644
--- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPACountRequestProcessor.java
+++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPACountRequestProcessor.java
@@ -35,7 +35,7 @@ public void retrieveData(final ODataRequest request, final ODataResponse respons
if (uriResource instanceof UriResourceEntitySet
|| uriResource instanceof UriResourceSingleton) {
final var result = countEntities();
- createSuccessResponse(response, ContentType.TEXT_PLAIN, serializer.serialize(request, result));
+ createSuccessResponse(response, ContentType.TEXT_PLAIN, serializer.serialize(request, result), null);
} else {
throw new ODataJPAProcessorException(ODataJPAProcessorException.MessageKeys.NOT_SUPPORTED_RESOURCE_TYPE,
HttpStatusCode.NOT_IMPLEMENTED, uriResource.getKind().toString());
diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAETagValidationResult.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAETagValidationResult.java
new file mode 100644
index 000000000..5d3785d22
--- /dev/null
+++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAETagValidationResult.java
@@ -0,0 +1,19 @@
+package com.sap.olingo.jpa.processor.core.processor;
+
+import org.apache.olingo.commons.api.http.HttpStatusCode;
+
+enum JPAETagValidationResult {
+ NOT_MODIFIED(HttpStatusCode.NOT_MODIFIED),
+ SUCCESS(HttpStatusCode.OK),
+ PRECONDITION_FAILED(HttpStatusCode.PRECONDITION_FAILED);
+
+ private final HttpStatusCode statusCode;
+
+ JPAETagValidationResult(final HttpStatusCode statusCode) {
+ this.statusCode = statusCode;
+ }
+
+ HttpStatusCode getStatusCode() {
+ return statusCode;
+ }
+}
diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAFunctionRequestProcessor.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAFunctionRequestProcessor.java
index 1b556cb88..5121d96c0 100644
--- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAFunctionRequestProcessor.java
+++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAFunctionRequestProcessor.java
@@ -50,9 +50,11 @@ public void retrieveData(final ODataRequest request, final ODataResponse respons
.getFunction().getName());
Object result = null;
if (jpaFunction.getFunctionType() == EdmFunctionType.JavaClass) {
- result = new JPAJavaFunctionProcessor(sd, uriResourceFunction, (JPAJavaFunction) jpaFunction, em).process();
+ result = new JPAJavaFunctionProcessor(sd, uriResourceFunction, (JPAJavaFunction) jpaFunction,
+ em, requestContext.getHeader(), requestContext.getRequestParameter()).process();
} else if (jpaFunction.getFunctionType() == EdmFunctionType.UserDefinedFunction) {
- result = dbProcessor.executeFunctionQuery(uriInfo.getUriResourceParts(), (JPADataBaseFunction) jpaFunction, em);
+ result = dbProcessor.executeFunctionQuery(uriInfo.getUriResourceParts(), (JPADataBaseFunction) jpaFunction, em,
+ requestContext.getHeader(), requestContext.getRequestParameter());
}
final EdmType returnType = uriResourceFunction.getFunction().getReturnType().getType();
final Annotatable annotatable = convertResult(result, returnType, jpaFunction);
diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPANavigationRequestProcessor.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPANavigationRequestProcessor.java
index 082d30780..1bafa1afd 100644
--- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPANavigationRequestProcessor.java
+++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPANavigationRequestProcessor.java
@@ -4,14 +4,20 @@
import static com.sap.olingo.jpa.processor.core.exception.ODataJPAProcessorException.MessageKeys.ODATA_MAXPAGESIZE_NOT_A_NUMBER;
import static com.sap.olingo.jpa.processor.core.exception.ODataJPAProcessorException.MessageKeys.QUERY_PREPARATION_ERROR;
import static com.sap.olingo.jpa.processor.core.exception.ODataJPAProcessorException.MessageKeys.QUERY_RESULT_CONV_ERROR;
+import static com.sap.olingo.jpa.processor.core.exception.ODataJPAProcessorException.MessageKeys.VALIDATION_NOT_POSSIBLE_TOO_MANY_RESULTS;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Optional;
+import jakarta.persistence.Tuple;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
import org.apache.olingo.commons.api.data.ComplexValue;
import org.apache.olingo.commons.api.data.Entity;
import org.apache.olingo.commons.api.data.EntityCollection;
@@ -19,20 +25,24 @@
import org.apache.olingo.commons.api.edm.EdmEntitySet;
import org.apache.olingo.commons.api.ex.ODataException;
import org.apache.olingo.commons.api.format.ContentType;
+import org.apache.olingo.commons.api.http.HttpHeader;
import org.apache.olingo.commons.api.http.HttpStatusCode;
import org.apache.olingo.server.api.OData;
import org.apache.olingo.server.api.ODataApplicationException;
import org.apache.olingo.server.api.ODataRequest;
import org.apache.olingo.server.api.ODataResponse;
import org.apache.olingo.server.api.ServiceMetadata;
+import org.apache.olingo.server.api.etag.PreconditionException;
import org.apache.olingo.server.api.uri.UriInfoResource;
import org.apache.olingo.server.api.uri.UriResource;
import org.apache.olingo.server.api.uri.UriResourceKind;
import org.apache.olingo.server.api.uri.UriResourcePartTyped;
import org.apache.olingo.server.api.uri.queryoption.SystemQueryOptionKind;
+import com.sap.olingo.jpa.metadata.api.JPAHttpHeaderMap;
import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAnnotatable;
import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAssociationPath;
+import com.sap.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelException;
import com.sap.olingo.jpa.processor.core.api.JPAODataPage;
import com.sap.olingo.jpa.processor.core.api.JPAODataRequestContextAccess;
import com.sap.olingo.jpa.processor.core.converter.JPAExpandResult;
@@ -40,8 +50,10 @@
import com.sap.olingo.jpa.processor.core.exception.ODataJPANotImplementedException;
import com.sap.olingo.jpa.processor.core.exception.ODataJPAProcessException;
import com.sap.olingo.jpa.processor.core.exception.ODataJPAProcessorException;
+import com.sap.olingo.jpa.processor.core.exception.ODataJPAQueryException;
import com.sap.olingo.jpa.processor.core.query.JPACollectionItemInfo;
import com.sap.olingo.jpa.processor.core.query.JPACollectionJoinQuery;
+import com.sap.olingo.jpa.processor.core.query.JPAConvertibleResult;
import com.sap.olingo.jpa.processor.core.query.JPAExpandItemInfo;
import com.sap.olingo.jpa.processor.core.query.JPAExpandItemInfoFactory;
import com.sap.olingo.jpa.processor.core.query.JPAExpandQueryFactory;
@@ -52,6 +64,7 @@
import com.sap.olingo.jpa.processor.core.query.Utility;
public final class JPANavigationRequestProcessor extends JPAAbstractGetRequestProcessor {
+ private static final Log LOGGER = LogFactory.getLog(JPANavigationRequestProcessor.class);
private final ServiceMetadata serviceMetadata;
private final UriResource lastItem;
private final JPAODataPage page;
@@ -83,15 +96,19 @@ public > void retrieveData(final ODataRequest request, f
}
final var result = query.execute();
+ // Validate If-Match and If-None-Match headers
+ final var conditionValidationResult = validateEntityTag(result, requestContext.getHeader());
// Read Expand and Collection
- final var keyBoundary = result.getKeyBoundary(requestContext, query.getNavigationInfo(),
- page);
- final var watchDog = new JPAExpandWatchDog(determineTargetEntitySet(requestContext));
- watchDog.watch(uriInfo.getExpandOption(), uriInfo.getUriResourceParts());
- result.putChildren(readExpandEntities(request.getAllHeaders(), query.getNavigationInfo(), uriInfo, keyBoundary,
- watchDog));
- // Convert tuple result into an OData Result
EntityCollection entityCollection;
+ if (conditionValidationResult == JPAETagValidationResult.SUCCESS) {
+ final var keyBoundary = result.getKeyBoundary(requestContext, query.getNavigationInfo(),
+ page);
+ final var watchDog = new JPAExpandWatchDog(determineTargetEntitySet(requestContext));
+ watchDog.watch(uriInfo.getExpandOption(), uriInfo.getUriResourceParts());
+ result.putChildren(readExpandEntities(request.getAllHeaders(), query.getNavigationInfo(), uriInfo, keyBoundary,
+ watchDog));
+ }
+ // Convert tuple result into an OData Result
try (var converterMeasurement = debugger.newMeasurement(this, "convertResult")) {
entityCollection = result.asEntityCollection(new JPATupleChildConverter(sd, odata.createUriHelper(),
serviceMetadata, requestContext)).get(ROOT_RESULT_KEY);
@@ -132,7 +149,11 @@ public > void retrieveData(final ODataRequest request, f
* of
* the related single entity. If no entity is related, the service returns 204 No Content.
*/
- if (hasNoContent(entityCollection.getEntities()))
+ if (conditionValidationResult == JPAETagValidationResult.NOT_MODIFIED)
+ createNotModifiedResponse(response, entityCollection);
+ else if (conditionValidationResult == JPAETagValidationResult.PRECONDITION_FAILED)
+ createPreconditionFailedResponse(response);
+ else if (hasNoContent(entityCollection.getEntities()))
response.setStatusCode(HttpStatusCode.NO_CONTENT.getStatusCode());
else if (doesNotExists(entityCollection.getEntities()))
response.setStatusCode(HttpStatusCode.NOT_FOUND.getStatusCode());
@@ -140,7 +161,7 @@ else if (doesNotExists(entityCollection.getEntities()))
else if (entityCollection.getEntities() != null) {
try (var serializerMeasurement = debugger.newMeasurement(this, "serialize")) {
final var serializerResult = serializer.serialize(request, entityCollection);
- createSuccessResponse(response, responseFormat, serializerResult);
+ createSuccessResponse(response, responseFormat, serializerResult, entityCollection);
}
} else {
// A request returns 204 No Content if the requested resource has the null value, or if the service applies a
@@ -150,11 +171,69 @@ else if (entityCollection.getEntities() != null) {
}
}
- private void checkRequestSupported() throws ODataJPAProcessException {
+ void checkRequestSupported() throws ODataJPAProcessException {
if (uriInfo.getApplyOption() != null)
throw new ODataJPANotImplementedException("$apply");
}
+ /**
+ * Validate
+ * If-Match and
+ * If-None-Match
+ * headers
+ * @param result Query result
+ * @param header List of all request headers
+ * @throws ODataJPAProcessorException
+ */
+ JPAETagValidationResult validateEntityTag(final JPAConvertibleResult result, final JPAHttpHeaderMap header)
+ throws ODataJPAProcessorException {
+
+ try {
+ if (result instanceof final JPAExpandResult expandResult) {
+ if (expandResult.getEntityType().hasEtag()) {
+
+ final var results = expandResult.getResult(ROOT_RESULT_KEY);
+ final var ifNoneMatchEntityTags = getMatchHeader(header, HttpHeader.IF_NONE_MATCH);
+ final var ifMatchEntityTags = getMatchHeader(header, HttpHeader.IF_MATCH);
+ if (results.size() == 1) {
+ final var etagAlias = expandResult.getEntityType().getEtagPath().getAlias();
+ final var etag = requestContext.getEtagHelper().asEtag(expandResult.getEntityType(),
+ results.get(0).get(etagAlias));
+ if (requestContext.getEtagHelper().checkReadPreconditions(etag, ifMatchEntityTags, ifNoneMatchEntityTags))
+ return JPAETagValidationResult.NOT_MODIFIED;
+ } else if (!preconditionCheckSupported(results, ifNoneMatchEntityTags, ifMatchEntityTags)) {
+ throw new ODataJPAProcessorException(VALIDATION_NOT_POSSIBLE_TOO_MANY_RESULTS,
+ HttpStatusCode.BAD_REQUEST);
+ }
+ }
+ } else {
+ LOGGER.warn("Result is not of type JPAExpandResult. ETag validation not possible");
+ }
+ } catch (final ODataJPAModelException | ODataJPAQueryException e) {
+ throw new ODataJPAProcessorException(e, HttpStatusCode.INTERNAL_SERVER_ERROR);
+ }
+
+ catch (final PreconditionException e) {
+ return JPAETagValidationResult.PRECONDITION_FAILED;
+ }
+ return JPAETagValidationResult.SUCCESS;
+ }
+
+ private List getMatchHeader(final JPAHttpHeaderMap headers, final String matchHeader) {
+ return Optional.ofNullable(headers.get(matchHeader)).orElseGet(() -> headers.get(
+ matchHeader.toLowerCase(Locale.ENGLISH)));
+ }
+
+ private boolean preconditionCheckSupported(final List results, final List ifNoneMatchEntityTags,
+ final List ifMatchEntityTags) {
+ return results.size() <= 1
+ || (isEmpty(ifMatchEntityTags) && isEmpty(ifNoneMatchEntityTags));
+ }
+
+ private boolean isEmpty(final List list) {
+ return list == null || list.isEmpty();
+ }
+
private URI buildNextLink(final JPAODataPage page) throws ODataJPAProcessorException {
if (page != null && page.skipToken() != null) {
try {
diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAODataEtagHelperImpl.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAODataEtagHelperImpl.java
new file mode 100644
index 000000000..428e23803
--- /dev/null
+++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAODataEtagHelperImpl.java
@@ -0,0 +1,58 @@
+package com.sap.olingo.jpa.processor.core.processor;
+
+import java.sql.Timestamp;
+import java.util.Collection;
+
+import org.apache.olingo.commons.api.http.HttpStatusCode;
+import org.apache.olingo.server.api.OData;
+import org.apache.olingo.server.api.etag.ETagHelper;
+import org.apache.olingo.server.api.etag.PreconditionException;
+
+import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEntityType;
+import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEtagValidator;
+import com.sap.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelException;
+import com.sap.olingo.jpa.processor.core.api.JPAODataEtagHelper;
+import com.sap.olingo.jpa.processor.core.exception.ODataJPAQueryException;
+
+final class JPAODataEtagHelperImpl implements JPAODataEtagHelper {
+
+ private final ETagHelper olingoHelper;
+
+ JPAODataEtagHelperImpl(final OData odata) {
+ this.olingoHelper = odata.createETagHelper();
+ }
+
+ @Override
+ public boolean checkReadPreconditions(final String etag, final Collection ifMatchHeaders,
+ final Collection ifNoneMatchHeaders) throws PreconditionException {
+ return olingoHelper.checkReadPreconditions(etag, ifMatchHeaders, ifNoneMatchHeaders);
+ }
+
+ @Override
+ public void checkChangePreconditions(final String etag, final Collection ifMatchHeaders,
+ final Collection ifNoneMatchHeaders) throws PreconditionException {
+ olingoHelper.checkChangePreconditions(etag, ifMatchHeaders, ifNoneMatchHeaders);
+ }
+
+ @Override
+ public String asEtag(final JPAEntityType entityType, final Object value) throws ODataJPAQueryException {
+
+ try {
+ if (entityType.hasEtag()) {
+ final var etag = new StringBuilder();
+ if (value != null) {
+ if (entityType.getEtagValidator() == JPAEtagValidator.WEAK)
+ etag.append("W/");
+ etag.append("\"")
+ .append(value instanceof final Timestamp t ? t.toInstant().toString() : value.toString())
+ .append("\"");
+ }
+ return etag.toString();
+ }
+ return null;
+ } catch (final ODataJPAModelException e) {
+ throw new ODataJPAQueryException(e, HttpStatusCode.INTERNAL_SERVER_ERROR);
+ }
+ }
+
+}
diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAODataInternalRequestContext.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAODataInternalRequestContext.java
index 65fe065d4..15c2638b2 100644
--- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAODataInternalRequestContext.java
+++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAODataInternalRequestContext.java
@@ -17,6 +17,7 @@
import jakarta.persistence.EntityManager;
import org.apache.olingo.commons.api.ex.ODataException;
+import org.apache.olingo.server.api.OData;
import org.apache.olingo.server.api.uri.UriInfo;
import org.apache.olingo.server.api.uri.UriInfoResource;
@@ -32,6 +33,7 @@
import com.sap.olingo.jpa.processor.core.api.JPAODataClaimProvider;
import com.sap.olingo.jpa.processor.core.api.JPAODataDatabaseProcessor;
import com.sap.olingo.jpa.processor.core.api.JPAODataDefaultTransactionFactory;
+import com.sap.olingo.jpa.processor.core.api.JPAODataEtagHelper;
import com.sap.olingo.jpa.processor.core.api.JPAODataGroupProvider;
import com.sap.olingo.jpa.processor.core.api.JPAODataPage;
import com.sap.olingo.jpa.processor.core.api.JPAODataQueryDirectives;
@@ -68,13 +70,15 @@ public final class JPAODataInternalRequestContext implements JPAODataRequestCont
private Optional edmProvider;
private JPAODataDatabaseOperations operationConverter;
private JPAODataQueryDirectives queryDirectives;
+ private JPAODataEtagHelper etagHelper;
public JPAODataInternalRequestContext(@Nonnull final JPAODataRequestContext requestContext,
- @Nonnull final JPAODataSessionContextAccess sessionContext) {
+ @Nonnull final JPAODataSessionContextAccess sessionContext, final OData odata) {
this.header = new JPAHttpHeaderHashMap(Collections.emptyMap());
copyRequestContext(requestContext, sessionContext);
this.hookFactory = new JPAHookFactory(em, header, customParameter);
initDebugger();
+ etagHelper = new JPAODataEtagHelperImpl(odata);
}
/**
@@ -216,6 +220,11 @@ public List getProvidedLocale() {
return locales;
}
+ @Override
+ public JPAODataEtagHelper getEtagHelper() {
+ return etagHelper;
+ }
+
public void setEntityManager(@Nonnull final EntityManager em) {
this.em = Objects.requireNonNull(em);
}
@@ -275,6 +284,7 @@ private void copyContextValues(final JPAODataRequestContextAccess context)
this.edmProvider = Optional.ofNullable(context.getEdmProvider());
this.operationConverter = context.getOperationConverter();
this.queryDirectives = context.getQueryDirectives();
+ this.etagHelper = context.getEtagHelper();
}
private void copyRequestContext(@Nonnull final JPAODataRequestContext requestContext,
diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAOperationRequestProcessor.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAOperationRequestProcessor.java
index 3357a90a8..76e00b58a 100644
--- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAOperationRequestProcessor.java
+++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAOperationRequestProcessor.java
@@ -2,13 +2,12 @@
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Parameter;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
-import jakarta.persistence.EntityManager;
-
import org.apache.olingo.commons.api.data.Annotatable;
import org.apache.olingo.commons.api.data.ComplexValue;
import org.apache.olingo.commons.api.data.EntityCollection;
@@ -106,18 +105,19 @@ private ComplexValue createComplexValue(final EdmComplexType returnType, final O
@SuppressWarnings({ "rawtypes", "unchecked" })
private EntityCollection createEntityCollection(final EdmEntityType returnType, final Object result,
- final UriHelper createUriHelper, final JPAOperation jpaFunction)
+ final UriHelper createUriHelper, final JPAOperation jpaOperation)
throws ODataApplicationException {
final List resultList = new ArrayList();
- if (jpaFunction.getResultParameter().isCollection())
+ if (jpaOperation.getResultParameter().isCollection())
resultList.addAll((Collection>) result);
else if (result == null)
return null;
else
resultList.add(result);
try {
- return new JPAEntityResultConverter(createUriHelper, sd, resultList, returnType).getResult();
+ return new JPAEntityResultConverter(createUriHelper, sd, resultList, returnType, requestContext.getEtagHelper())
+ .getResult();
} catch (SerializerException | ODataJPAModelException | URISyntaxException e) {
throw new ODataJPAProcessorException(ODataJPAProcessorException.MessageKeys.QUERY_RESULT_CONV_ERROR,
HttpStatusCode.INTERNAL_SERVER_ERROR, e);
@@ -131,22 +131,33 @@ protected void serializeResult(final EdmType returnType, final ODataResponse res
if (result != null
&& !(result instanceof final EntityCollection collection
&& collection.getEntities().isEmpty())) {
+ final EntityCollection collection = result instanceof EntityCollection // NOSONAR
+ ? (EntityCollection) result
+ : new EntityCollection();
final SerializerResult serializerResult = ((JPAOperationSerializer) serializer).serialize(result, returnType,
request);
- createSuccessResponse(response, responseFormat, serializerResult);
+ createSuccessResponse(response, responseFormat, serializerResult, collection);
} else {
response.setStatusCode(HttpStatusCode.NO_CONTENT.getStatusCode());
}
}
- protected Object createInstance(final EntityManager em, final JPAJavaOperation jpaOperation)
+ protected Object createInstance(final JPAJavaOperation jpaOperation, final Object... parameters)
throws InstantiationException, IllegalAccessException, InvocationTargetException {
final Constructor> constructor = jpaOperation.getConstructor();
- if (constructor.getParameterCount() == 1)
- return constructor.newInstance(em);
- else
- return constructor.newInstance();
+ final Object[] paramValues = new Object[constructor.getParameters().length];
+ int i = 0;
+ for (final Parameter p : constructor.getParameters()) {
+ for (final Object o : parameters) {
+ if (p.getType().isAssignableFrom(o.getClass())) {
+ paramValues[i] = o;
+ break;
+ }
+ }
+ i++;
+ }
+ return constructor.newInstance(paramValues);
}
}
diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAProcessorFactory.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAProcessorFactory.java
index 4b16cce07..0cdc79cc3 100644
--- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAProcessorFactory.java
+++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAProcessorFactory.java
@@ -9,6 +9,8 @@
import java.util.Map.Entry;
import java.util.Optional;
+import javax.annotation.Nonnull;
+
import org.apache.olingo.commons.api.ex.ODataException;
import org.apache.olingo.commons.api.format.ContentType;
import org.apache.olingo.commons.api.http.HttpHeader;
@@ -100,7 +102,8 @@ public JPARequestProcessor createProcessor(final UriInfo uriInfo, final ContentT
checkNavigationPathSupported(resourceParts);
yield new JPANavigationRequestProcessor(odata, serviceMetadata, requestContext);
}
- default -> throw new ODataJPAProcessorException(ODataJPAProcessorException.MessageKeys.NOT_SUPPORTED_RESOURCE_TYPE,
+ default -> throw new ODataJPAProcessorException(
+ ODataJPAProcessorException.MessageKeys.NOT_SUPPORTED_RESOURCE_TYPE,
HttpStatusCode.NOT_IMPLEMENTED, lastItem.getKind().toString());
};
} catch (final ODataJPAIllegalAccessException e) {
@@ -128,6 +131,7 @@ private void checkNavigationPathSupported(final List resourceParts)
}
}
+ @Nonnull
private JPAODataPage getPage(final Map> headers, final UriInfo uriInfo,
final JPAODataRequestContextAccess requestContext, final JPAODataPathInformation pathInformation)
throws ODataException {
diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAAbstractJoinQuery.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAAbstractJoinQuery.java
index c03eefdf0..789b7802d 100644
--- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAAbstractJoinQuery.java
+++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAAbstractJoinQuery.java
@@ -44,8 +44,6 @@
import org.apache.olingo.server.api.uri.UriResourceProperty;
import org.apache.olingo.server.api.uri.queryoption.SelectItem;
import org.apache.olingo.server.api.uri.queryoption.SelectOption;
-import org.apache.olingo.server.api.uri.queryoption.SkipOption;
-import org.apache.olingo.server.api.uri.queryoption.TopOption;
import org.apache.olingo.server.api.uri.queryoption.expression.ExpressionVisitException;
import com.sap.olingo.jpa.metadata.core.edm.annotation.EdmQueryExtensionProvider;
@@ -77,24 +75,14 @@ public abstract class JPAAbstractJoinQuery extends JPAAbstractQuery implements J
protected static final String ALIAS_SEPARATOR = ".";
protected final UriInfoResource uriResource;
protected final CriteriaQuery cq;
- protected Root> root; // Start of an navigation
- protected From, ?> target; // The entity that shall be returned by the query
protected final JPAODataPage page;
protected final List navigationInfo;
protected final JPANavigationPropertyInfo lastInfo;
protected final JPAODataRequestContextAccess requestContext;
+ protected Root> root; // Start of a navigation
+ protected From, ?> target; // The entity that shall be returned by the query
protected Optional entitySet;
- protected static Optional determineTargetEntitySet(final JPAODataRequestContextAccess requestContext)
- throws ODataException {
-
- final EdmBindingTarget bindingTarget = Utility.determineBindingTarget(requestContext.getUriInfo()
- .getUriResourceParts());
- if (bindingTarget instanceof EdmEntitySet)
- return requestContext.getEdmProvider().getServiceDocument().getEntitySet(bindingTarget.getName());
- return Optional.empty();
- }
-
JPAAbstractJoinQuery(final OData odata, final JPAEntityType jpaEntityType,
final JPAODataRequestContextAccess requestContext, final List navigationInfo)
throws ODataException {
@@ -117,6 +105,16 @@ protected static Optional determineTargetEntitySet(final JPAODataR
this.entitySet = Optional.empty();
}
+ protected static Optional determineTargetEntitySet(final JPAODataRequestContextAccess requestContext)
+ throws ODataException {
+
+ final EdmBindingTarget bindingTarget = Utility.determineBindingTarget(requestContext.getUriInfo()
+ .getUriResourceParts());
+ if (bindingTarget instanceof EdmEntitySet)
+ return requestContext.getEdmProvider().getServiceDocument().getEntitySet(bindingTarget.getName());
+ return Optional.empty();
+ }
+
@SuppressWarnings("unchecked")
@Override
public AbstractQuery getQuery() {
@@ -204,6 +202,7 @@ else if (select == null || select.getSelectItems().isEmpty() || select.getSelect
*
A stream is requested and the property contains the mime type>
*
* Not included are collection properties.
+ *
* @param uriResource
* @return
* @throws ODataApplicationException
@@ -253,12 +252,10 @@ protected > jakarta.persistence.criteria.Express
}
/**
- *
* @param orderByTarget
- * @param descriptionFields List of the requested fields that of type description
+ * @param selectionPath
* @param query
* @param lastInfo
- * @param queryRoot
* @return
* @throws ODataApplicationException
* @throws JPANoSelectionException
@@ -368,31 +365,31 @@ protected final boolean determineTargetIsCollection(final UriInfoResource uriRes
return (last instanceof final UriResourceProperty property && property.isCollection());
}
- protected void expandPath(final JPAEntityType jpaEntity, final SelectionPathInfo jpaPathList,
+ protected void expandPath(final JPAStructuredType st, final SelectionPathInfo jpaPathList,
final String selectItem, final boolean targetIsCollection) throws ODataJPAModelException,
ODataJPAProcessException {
- final JPAPath selectItemPath = jpaEntity.getPath(selectItem);
+ final JPAPath selectItemPath = st.getPath(selectItem);
if (selectItemPath == null)
throw new ODataJPAQueryException(QUERY_PREPARATION_INVALID_SELECTION_PATH, BAD_REQUEST);
if (selectItemPath.getLeaf().isComplex()) {
- expandComplexPath(jpaEntity, jpaPathList, targetIsCollection, selectItemPath);
+ expandComplexPath(st, jpaPathList, targetIsCollection, selectItemPath);
} else if (selectItemPath.isTransient()) {
- addTransientAttribute(jpaEntity, jpaPathList, selectItemPath);
+ addTransientAttribute(st, jpaPathList, selectItemPath);
} else if (!selectItemPath.getLeaf().isCollection()
|| targetIsCollection) {// Primitive Type
jpaPathList.getODataSelections().add(selectItemPath);
}
}
- private void expandComplexPath(final JPAEntityType jpaEntity, final SelectionPathInfo jpaPathList,
+ private void expandComplexPath(final JPAStructuredType st, final SelectionPathInfo jpaPathList,
final boolean targetIsCollection, final JPAPath selectItemPath) throws ODataJPAModelException,
ODataJPAProcessorException {
- final List child = jpaEntity.searchChildPath(selectItemPath);
+ final List child = st.searchChildPath(selectItemPath);
if (targetIsCollection) {
for (final JPAPath p : child) {
if (p.isTransient())
- addTransientAttribute(jpaEntity, jpaPathList, p);
+ addTransientAttribute(st, jpaPathList, p);
else
jpaPathList.getODataSelections().add(p);
}
@@ -401,9 +398,9 @@ private void expandComplexPath(final JPAEntityType jpaEntity, final SelectionPat
}
}
- private void addTransientAttribute(final JPAEntityType jpaEntity, final SelectionPathInfo jpaPathList,
+ private void addTransientAttribute(final JPAStructuredType st, final SelectionPathInfo jpaPathList,
final JPAPath path) throws ODataJPAModelException, ODataJPAProcessorException {
- buildRequiredSelections(jpaEntity, path, jpaPathList.getRequiredSelections());
+ buildRequiredSelections(st, path, jpaPathList.getRequiredSelections());
jpaPathList.getTransientSelections().add(path);
}
@@ -450,29 +447,26 @@ private void addCollection(final Map> joinTables, final JPAPa
}
private void addSkip(final TypedQuery typedQuery) throws ODataJPAQueryException {
- final SkipOption skipOption = uriResource.getSkipOption();
- if (skipOption != null || page != null) {
- int skipNumber = skipOption != null ? skipOption.getValue() : page.skip();
- skipNumber = skipOption != null && page != null ? Math.max(skipOption.getValue(), page.skip()) : skipNumber;
- if (skipNumber >= 0)
- typedQuery.setFirstResult(skipNumber);
- else
- throw new ODataJPAQueryException(ODataJPAQueryException.MessageKeys.QUERY_PREPARATION_INVALID_VALUE,
- HttpStatusCode.BAD_REQUEST, Integer.toString(skipNumber), "$skip");
+ // Paging provider has to respect $skip. With the JPADefaultPagingProvider there shall be
+ // always a page
+ if (page != null) {
+ if (page.skip() > 0)
+ typedQuery.setFirstResult(page.skip());
+ } else {
+ throw new ODataJPAQueryException(ODataJPAQueryException.MessageKeys.QUERY_PREPARATION_NO_PAGE_FOUND,
+ HttpStatusCode.INTERNAL_SERVER_ERROR, "$skip");
}
}
- private void addTop(final TypedQuery tupleQuery) throws ODataJPAQueryException {
- final TopOption topOption = uriResource.getTopOption();
- if (topOption != null || page != null) {
- int topNumber = topOption != null ? topOption.getValue() : page.top();
- topNumber = topOption != null && page != null ? Math.min(topOption.getValue(), page.top())
- : topNumber;
- if (topNumber >= 0)
- tupleQuery.setMaxResults(topNumber);
- else
- throw new ODataJPAQueryException(ODataJPAQueryException.MessageKeys.QUERY_PREPARATION_INVALID_VALUE,
- HttpStatusCode.BAD_REQUEST, Integer.toString(topNumber), "$top");
+ private void addTop(final TypedQuery typedQuery) throws ODataJPAQueryException {
+ // Paging provider has to respect $top. With the JPADefaultPagingProvider there shall be
+ // always a page
+ if (page != null) {
+ if (page.top() < Integer.MAX_VALUE)
+ typedQuery.setMaxResults(page.top());
+ } else {
+ throw new ODataJPAQueryException(ODataJPAQueryException.MessageKeys.QUERY_PREPARATION_NO_PAGE_FOUND,
+ HttpStatusCode.INTERNAL_SERVER_ERROR, "$top");
}
}
@@ -493,7 +487,7 @@ private List buildPathValue(final JPAEntityType jpaEntity)
return jpaPathList;
}
- private void buildRequiredSelections(final JPAEntityType et, final JPAPath transientAttributePath,
+ private void buildRequiredSelections(final JPAStructuredType et, final JPAPath transientAttributePath,
final Set requitedSelections) throws ODataJPAModelException, ODataJPAProcessorException {
final StringBuilder pathName = new StringBuilder();
@@ -511,8 +505,7 @@ private void buildRequiredSelections(final JPAEntityType et, final JPAPath trans
.orElseThrow(() -> new ODataJPAProcessorException(ATTRIBUTE_NOT_FOUND,
HttpStatusCode.INTERNAL_SERVER_ERROR, internalName))
.getExternalName();
- final StringBuilder requiredPathName = new StringBuilder(pathName.toString()).append(externalName);
- requitedSelections.add(et.getPath(requiredPathName.toString(), false));
+ requitedSelections.add(et.getPath(pathName + externalName, false));
}
}
@@ -524,9 +517,10 @@ private void buildSelectionAddETag(final JPAEntityType jpaEntity, final Collecti
}
/**
- * In order to be able to link the result of a expand query with the super-ordinate query it is necessary to ensure
+ * In order to be able to link the result of an expand query with the super-ordinate query it is necessary to ensure
* that the join columns are selected.
* The same columns are required for the count query, for select as well as order by.
+ *
* @param uriResource
* @param jpaPathList
* @throws ODataApplicationException
@@ -606,6 +600,7 @@ private void convertSelectIntoPath(final SelectOption select, final SelectionPat
/**
* Skips all those properties that are or belong to a collection property or marked as transient. E.g
* (Organization)Comment or (Person)InhouseAddress/Room
+ *
* @param selectablePathList
* @param allPathList
* @throws ODataJPAProcessorException
@@ -739,6 +734,7 @@ protected boolean isCollectionPropertyQuery() {
/**
* Completes NavigationInfo and add Joins for navigation parts e.g. from ../Organizations('3')/Roles
+ *
* @param joinTables
* @throws ODataJPAQueryException
* @throws ODataJPAProcessorException
@@ -779,6 +775,7 @@ protected final void createFromClauseNavigationJoins(final HashMap
* 4.11 Addressing Derived Types
*
+ *
* @param baseType
* @param potentialDerivedType
* @return true if potentialDerivedType is indeed a derived type of baseType
@@ -809,6 +806,7 @@ protected final void addFilterCompiler(final JPANavigationPropertyInfo navigatio
/**
* Start point of a Join Query e.g. triggered by ../Organizations or
* ../Organizations('3')/Roles
+ *
* @param query
* @param joinTables
* @throws ODataJPAQueryException
diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAAbstractQuery.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAAbstractQuery.java
index a895046fe..e368e9daf 100644
--- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAAbstractQuery.java
+++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAAbstractQuery.java
@@ -24,6 +24,8 @@
import java.util.Optional;
import java.util.Set;
+import javax.annotation.Nonnull;
+
import jakarta.persistence.EntityManager;
import jakarta.persistence.criteria.AbstractQuery;
import jakarta.persistence.criteria.CriteriaBuilder;
@@ -153,10 +155,11 @@ protected jakarta.persistence.criteria.Expression addWhereClause(
final jakarta.persistence.criteria.Expression additionalExpression) {
if (additionalExpression != null) {
- if (whereCondition == null)
+ if (whereCondition == null) {
whereCondition = additionalExpression;
- else
+ } else {
whereCondition = cb.and(whereCondition, additionalExpression);
+ }
}
return whereCondition;
}
@@ -184,20 +187,27 @@ protected void createFromClauseOrderBy(final List orderByTar
final Map> joinTables, final From, ?> from) {
for (final JPAAssociationPath orderBy : orderByTarget) {
From, ?> join = from;
- for (final JPAElement o : orderBy.getPath())
+ for (final JPAElement o : orderBy.getPath()) {
join = join.join(o.getInternalName(), JoinType.LEFT);
+ }
// Take on condition from JPA metadata; no explicit on
joinTables.put(orderBy.getAlias(), join);
}
}
protected List> createGroupBy(final Map> joinTables, // NOSONAR
- final From, ?> from, final Collection selectionPathList) {
+ final From, ?> from, final Collection selectionPathList, @Nonnull Set> orderByPaths) {
try (JPARuntimeMeasurement serializerMeasurement = debugger.newMeasurement(this, "createGroupBy")) {
final List> groupBy = new ArrayList<>();
for (final JPAPath jpaPath : selectionPathList) {
- groupBy.add(ExpressionUtility.convertToCriteriaPath(joinTables, from, jpaPath.getPath()));
+ var path = ExpressionUtility.convertToCriteriaPath(joinTables, from, jpaPath.getPath());
+ orderByPaths.remove(path);
+ groupBy.add(path);
+ }
+
+ for (var path : orderByPaths) {
+ groupBy.add(path);
}
return groupBy;
}
@@ -209,10 +219,11 @@ protected Join createJoinFromPath(final String alias, final List join = null;
JoinType joinType;
for (int i = 0; i < pathList.size(); i++) {
- if (i == pathList.size() - 1)
+ if (i == pathList.size() - 1) {
joinType = finalJoinType;
- else
+ } else {
joinType = JoinType.INNER;
+ }
if (i == 0) {
join = root.join(pathList.get(i).getInternalName(), joinType);
join.alias(alias);
@@ -272,8 +283,9 @@ protected jakarta.persistence.criteria.Expression createProtectionWhere
final Map> dummyJoinTables = new HashMap<>(1);
for (final JPAProtectionInfo protection : et.getProtections()) { // look for protected attributes
final List> values = claimsProvider.get().get(protection.getClaimName()); // NOSONAR
- if (values.isEmpty())
+ if (values.isEmpty()) {
throw new ODataJPAQueryException(MISSING_CLAIM, HttpStatusCode.FORBIDDEN);
+ }
if (!(containsAll(values))) {
final Path> path = ExpressionUtility.convertToCriteriaPath(dummyJoinTables, from, protection.getPath()
.getPath());
@@ -358,9 +370,11 @@ protected List> createWhereKeyInPathList(final JPAAssociationPath associationP
protected final List extractDescriptionAttributes(final Collection jpaPathList) {
final List result = new ArrayList<>();
- for (final JPAPath p : jpaPathList)
- if (p.getLeaf() instanceof JPADescriptionAttribute)
+ for (final JPAPath p : jpaPathList) {
+ if (p.getLeaf() instanceof JPADescriptionAttribute) {
result.add(p);
+ }
+ }
return result;
}
@@ -390,9 +404,10 @@ protected List extractOrderByNavigationAttributes(final Orde
.isCollection()) {
pathString.append(((UriResourceProperty) uriResource).getProperty().getName());
final JPAPath jpaPath = jpaEntity.getPath(pathString.toString());
- if (jpaPath.isTransient())
+ if (jpaPath.isTransient()) {
throw new ODataJPAQueryException(QUERY_PREPARATION_ORDER_BY_TRANSIENT, NOT_IMPLEMENTED, jpaPath
.getLeaf().toString());
+ }
navigationAttributes.add(((JPACollectionAttribute) jpaPath.getLeaf()).asAssociation());
} else if (uriResource instanceof final UriResourceProperty resourceProperty) {
@@ -403,10 +418,11 @@ protected List extractOrderByNavigationAttributes(final Orde
throw new ODataJPAQueryException(QUERY_RESULT_CONV_ERROR, INTERNAL_SERVER_ERROR, e);
}
}
- } else
+ } else {
// TODO Support methods like tolower for order by as well
throw new ODataJPAQueryException(QUERY_PREPARATION_ORDER_BY_NOT_SUPPORTED, BAD_REQUEST,
expression.toString());
+ }
}
}
debugger.trace(this, "The following navigation attributes in order by were found: %s", navigationAttributes
@@ -421,10 +437,11 @@ protected void generateDescriptionJoin(final Map> joinTables,
final JPADescriptionAttribute descriptionField = ((JPADescriptionAttribute) descriptionFieldPath.getLeaf());
final Join, ?> join = createJoinFromPath(descriptionFieldPath.getAlias(), descriptionFieldPath.getPath(),
target, JoinType.LEFT);
- if (descriptionField.isLocationJoin())
+ if (descriptionField.isLocationJoin()) {
join.on(createOnCondition(join, descriptionField, getLocale().toString()));
- else
+ } else {
join.on(createOnCondition(join, descriptionField, getLocale().getLanguage()));
+ }
joinTables.put(descriptionField.getInternalName(), join);
}
}
@@ -436,10 +453,11 @@ protected jakarta.persistence.criteria.Expression orWhereClause(
final jakarta.persistence.criteria.Expression additionalExpression) {
if (additionalExpression != null) {
- if (whereCondition == null)
+ if (whereCondition == null) {
whereCondition = additionalExpression;
- else
+ } else {
whereCondition = cb.or(whereCondition, additionalExpression);
+ }
}
return whereCondition;
}
@@ -454,8 +472,9 @@ Set determineAllDescriptionPath(final List descriptionFields,
final Set allPath = new HashSet<>(descriptionFields);
if (filter != null) {
for (final JPAPath path : filter.getMember()) {
- if (path.getLeaf() instanceof JPADescriptionAttribute)
+ if (path.getLeaf() instanceof JPADescriptionAttribute) {
allPath.add(path);
+ }
}
}
return allPath;
@@ -472,8 +491,9 @@ private Expression createOnCondition(final Join, ?> join, final JPADe
final String localValue) {
final Predicate existingOn = join.getOn();
Expression result = cb.equal(determineLocalePath(join, descriptionField.getLocaleFieldName()), localValue);
- if (existingOn != null)
+ if (existingOn != null) {
result = cb.and(existingOn, result);
+ }
for (final JPAPath value : descriptionField.getFixedValueAssignment().keySet()) {
result = cb.and(result,
cb.equal(determineLocalePath(join, value), descriptionField.getFixedValueAssignment().get(value)));
@@ -489,16 +509,18 @@ private jakarta.persistence.criteria.Expression createProtectionWhereFo
jakarta.persistence.criteria.Expression attributeRestriction = null;
for (final JPAClaimsPair> value : values) { // for each given claim value
if (value.hasUpperBoundary) {
- if (wildcardsSupported && containsWildcard((String) value.min))
+ if (wildcardsSupported && containsWildcard((String) value.min)) {
throw new ODataJPAQueryException(WILDCARD_UPPER_NOT_SUPPORTED, HttpStatusCode.BAD_REQUEST);
- else
+ } else {
attributeRestriction = orWhereClause(attributeRestriction, createBetween(value, path));
+ }
} else {
- if (wildcardsSupported && containsWildcard((String) value.min))
+ if (wildcardsSupported && containsWildcard((String) value.min)) {
attributeRestriction = orWhereClause(attributeRestriction, cb.like((Path) path,
((String) value.min).replace('*', '%').replace('+', '_')));
- else
+ } else {
attributeRestriction = orWhereClause(attributeRestriction, cb.equal(path, value.min));
+ }
}
}
return attributeRestriction;
diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPACollectionExpandWrapper.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPACollectionExpandWrapper.java
index 58b9be219..c3e5c04f0 100644
--- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPACollectionExpandWrapper.java
+++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPACollectionExpandWrapper.java
@@ -15,21 +15,34 @@
import org.apache.olingo.server.api.uri.queryoption.IdOption;
import org.apache.olingo.server.api.uri.queryoption.OrderByOption;
import org.apache.olingo.server.api.uri.queryoption.SearchOption;
+import org.apache.olingo.server.api.uri.queryoption.SelectItem;
import org.apache.olingo.server.api.uri.queryoption.SelectOption;
import org.apache.olingo.server.api.uri.queryoption.SkipOption;
import org.apache.olingo.server.api.uri.queryoption.SkipTokenOption;
+import org.apache.olingo.server.api.uri.queryoption.SystemQueryOptionKind;
import org.apache.olingo.server.api.uri.queryoption.TopOption;
+import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAssociationPath;
+import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPACollectionAttribute;
import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEntityType;
public class JPACollectionExpandWrapper implements JPAExpandItem {
private final JPAEntityType jpaEntityType;
private final UriInfoResource uriInfo;
+ private final SelectOption selectOption;
- JPACollectionExpandWrapper(final JPAEntityType jpaEntityType, final UriInfoResource uriInfo) {
+ JPACollectionExpandWrapper(final JPAEntityType jpaEntityType, final UriInfoResource uriInfo,
+ final JPAAssociationPath associationPath) {
super();
this.jpaEntityType = jpaEntityType;
this.uriInfo = uriInfo;
+ this.selectOption = buildSelectOption(uriInfo.getSelectOption(), associationPath);
+ }
+
+ public JPACollectionExpandWrapper(final JPAEntityType jpaEntityType, final UriInfoResource uriInfo) {
+ this.jpaEntityType = jpaEntityType;
+ this.uriInfo = uriInfo;
+ this.selectOption = uriInfo.getSelectOption();
}
@Override
@@ -79,7 +92,7 @@ public SearchOption getSearchOption() {
@Override
public SelectOption getSelectOption() {
- return uriInfo.getSelectOption();
+ return selectOption;
}
@Override
@@ -108,7 +121,7 @@ public List getUriResourceParts() {
}
@Override
- public String getValueForAlias(String alias) {
+ public String getValueForAlias(final String alias) {
return null;
}
@@ -117,4 +130,56 @@ public JPAEntityType getEntityType() {
return jpaEntityType;
}
-}
+ private SelectOption buildSelectOption(final SelectOption selectOption, final JPAAssociationPath associationPath) {
+ final JPACollectionAttribute collectionAttribute = (JPACollectionAttribute) associationPath.getLeaf();
+ final List itemsOfProperty = selectOption.getSelectItems().stream()
+ .filter(item -> containsCollectionProperty(collectionAttribute, item.getResourcePath()))
+ .toList();
+ return new SelectOptionImpl(selectOption.getKind(), selectOption.getName(), selectOption.getText(),
+ itemsOfProperty);
+ }
+
+ private boolean containsCollectionProperty(final JPACollectionAttribute collectionAttribute,
+ final UriInfoResource resourcePath) {
+ return resourcePath.getUriResourceParts().stream()
+ .anyMatch(part -> part.getSegmentValue().equals(collectionAttribute.getExternalName()));
+ }
+
+ private static class SelectOptionImpl implements SelectOption {
+
+ private final SystemQueryOptionKind kind;
+ private final String name;
+ private final String text;
+ private final List items;
+
+ public SelectOptionImpl(final SystemQueryOptionKind kind, final String name, final String text,
+ final List itemsOfProperty) {
+ this.kind = kind;
+ this.name = name;
+ this.text = text;
+ this.items = itemsOfProperty;
+ }
+
+ @Override
+ public SystemQueryOptionKind getKind() {
+ return kind;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String getText() {
+ return text;
+ }
+
+ @Override
+ public List getSelectItems() {
+ return items;
+ }
+
+ }
+
+}
diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPACollectionItemInfo.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPACollectionItemInfo.java
index 5c71270f3..e2f6f72a2 100644
--- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPACollectionItemInfo.java
+++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPACollectionItemInfo.java
@@ -8,11 +8,11 @@
public final class JPACollectionItemInfo extends JPAInlineItemInfo {
JPACollectionItemInfo(final JPAServiceDocument sd, final JPAExpandItem uriInfo,
- final JPAAssociationPath expandAssociation, final List hops) {
+ final JPAAssociationPath expandAssociation, final List parentHops) {
- super(uriInfo, expandAssociation, hops);
+ super(uriInfo, expandAssociation, parentHops);
- for (JPANavigationPropertyInfo predecessor : hops)
+ for (final JPANavigationPropertyInfo predecessor : parentHops)
this.hops.add(new JPANavigationPropertyInfo(predecessor));
this.hops.get(this.hops.size() - 1).setAssociationPath(expandAssociation);
this.hops.add(new JPANavigationPropertyInfo(sd, expandAssociation, uriInfo, uriInfo.getEntityType()));
diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPACollectionJoinQuery.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPACollectionJoinQuery.java
index 5774b2826..6d926e484 100644
--- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPACollectionJoinQuery.java
+++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPACollectionJoinQuery.java
@@ -35,6 +35,7 @@
import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAttribute;
import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAElement;
import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAPath;
+import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAStructuredType;
import com.sap.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelException;
import com.sap.olingo.jpa.processor.core.api.JPAODataRequestContextAccess;
import com.sap.olingo.jpa.processor.core.api.JPAServiceDebugger.JPARuntimeMeasurement;
@@ -76,7 +77,7 @@ public JPACollectionQueryResult execute() throws ODataApplicationException {
protected SelectionPathInfo buildSelectionPathList(final UriInfoResource uriResource)
throws ODataApplicationException {
final SelectionPathInfo jpaPathList = new SelectionPathInfo<>();
- final String pathPrefix = "";
+ final String pathPrefix = Utility.determinePropertyNavigationPrefix(uriResource.getUriResourceParts());
final SelectOption select = uriResource.getSelectOption();
// Following situations have to be handled:
// - .../Organizations --> Select all collection attributes
@@ -86,23 +87,22 @@ protected SelectionPathInfo buildSelectionPathList(final UriInfoResourc
// - .../Persons('99')/InhouseAddress?$select=Building --> Select attributes of complex collection given by select
// clause
try {
+
if (SelectOptionUtil.selectAll(select))
// If the collection is part of a navigation take all the attributes
- expandPath(jpaEntity, jpaPathList, pathPrefix.isEmpty() ? this.association.getAlias() : pathPrefix
- + JPAPath.PATH_SEPARATOR + this.association.getAlias(), true);
+ expandPath(jpaEntity, jpaPathList, this.association.getAlias(), true);
else {
+ final var st = jpaEntity;
for (final SelectItem sItem : select.getSelectItems()) {
- final JPAPath selectItemPath = selectItemAsPath(pathPrefix, sItem);
+ final JPAPath selectItemPath = selectItemAsPath(st, pathPrefix, sItem);
if (pathContainsCollection(selectItemPath)) {
if (selectItemPath.getLeaf().isComplex()) {
- final JPAAttribute attribute = selectItemPath.getLeaf();
- expandPath(jpaEntity, jpaPathList, pathPrefix.isEmpty() ? attribute.getExternalName() : pathPrefix
- + JPAPath.PATH_SEPARATOR + attribute.getExternalName(), true);
+ expandPath(st, jpaPathList, selectItemPath.getAlias(), true);
} else {
jpaPathList.getODataSelections().add(selectItemPath);
}
} else if (selectItemPath.getLeaf().isComplex()) {
- expandPath(jpaEntity, jpaPathList, pathPrefix.isEmpty() ? this.association.getAlias() : pathPrefix
+ expandPath(st, jpaPathList, pathPrefix.isEmpty() ? this.association.getAlias() : pathPrefix
+ JPAPath.PATH_SEPARATOR + this.association.getAlias(), true);
}
}
@@ -113,15 +113,15 @@ protected SelectionPathInfo buildSelectionPathList(final UriInfoResourc
return jpaPathList;
}
- private JPAPath selectItemAsPath(final String pathPrefix, final SelectItem selectionItem)
- throws ODataJPAModelException,
- ODataJPAQueryException {
+ private JPAPath selectItemAsPath(final JPAStructuredType st, final String pathPrefix, final SelectItem selectionItem)
+ throws ODataJPAModelException, ODataJPAQueryException {
- String pathItem = selectionItem.getResourcePath().getUriResourceParts().stream().map(path -> (path
- .getSegmentValue())).collect(Collectors.joining(JPAPath.PATH_SEPARATOR));
+ String pathItem = selectionItem.getResourcePath().getUriResourceParts().stream()
+ .map(path -> (path.getSegmentValue()))
+ .collect(Collectors.joining(JPAPath.PATH_SEPARATOR));
pathItem = pathPrefix == null || pathPrefix.isEmpty() ? pathItem : pathPrefix + JPAPath.PATH_SEPARATOR
+ pathItem;
- final JPAPath selectItemPath = jpaEntity.getPath(pathItem);
+ final JPAPath selectItemPath = st.getPath(pathItem);
if (selectItemPath == null)
throw new ODataJPAQueryException(QUERY_PREPARATION_INVALID_SELECTION_PATH, BAD_REQUEST);
return selectItemPath;
@@ -157,6 +157,7 @@ protected List> createSelectClause(final Map> jo
/**
* Splits up a expand results, so it is returned as a map that uses a concatenation of the field values know by the
* parent.
+ *
* @param intermediateResult
* @param associationPath
* @param skip
diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandFilterQuery.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandFilterQuery.java
index 8c32d16c4..2a2f3a94b 100644
--- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandFilterQuery.java
+++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandFilterQuery.java
@@ -11,9 +11,11 @@
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.Set;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -127,13 +129,14 @@ public Subquery getSubQuery(@Nullable final Subquery> childQuery,
.getOrderByOption());
createRoots(childQuery, queries, nextQuery);
buildJoinTable(orderByAttributes, emptyList(), childQuery);
+ final Set> orderByPaths = new HashSet<>();
final List selections = selectionPathIn();
nextQuery.where(createWhere(childQuery));
nextQuery.multiselect(selectIn(childQuery, selections));
- nextQuery.orderBy(createOrderBy(childQuery));
+ nextQuery.orderBy(createOrderBy(childQuery, orderByPaths));
nextQuery.setFirstResult(getSkipValue(childQuery));
nextQuery.setMaxResults(getTopValue(childQuery));
- nextQuery.groupBy(createGroupBy(childQuery, orderByAttributes, selections));
+ nextQuery.groupBy(createGroupBy(joinTables, queryRoot, selections, orderByPaths));
return nextQuery;
}
}
@@ -156,18 +159,19 @@ protected final JPAFilterCrossComplier addFilterCompiler(final JPANavigationProp
protected Expression applyAdditionalFilter(final Expression where)
throws ODataApplicationException {
- if (navigationInfo.getFilterCompiler() != null && aggregationType == null)
+ if (navigationInfo.getFilterCompiler() != null && aggregationType == null) {
try {
return addWhereClause(where, navigationInfo.getFilterCompiler().compile());
} catch (final ExpressionVisitException e) {
throw new ODataJPAQueryException(e, HttpStatusCode.INTERNAL_SERVER_ERROR);
}
+
+ }
return where;
}
void buildJoinTable(final List orderByAttributes, final Collection selectionPath,
- final Subquery> childQuery)
- throws ODataApplicationException {
+ final Subquery> childQuery) throws ODataApplicationException {
createFromClauseJoinTable(joinTables, childQuery);
createFromClauseOrderBy(orderByAttributes, joinTables, queryRoot);
createFromClauseDescriptionFields(selectionPath, joinTables, queryRoot, singletonList(navigationInfo));
@@ -190,25 +194,17 @@ private void createFromClauseJoinTable(final Map> joinTables,
void setFilter(final JPANavigationPropertyInfo navigationInfo) throws ODataJPAModelException,
ODataJPAProcessorException,
ODataJPAQueryException {
- if (navigationInfo.getFilterCompiler() == null)
+ if (navigationInfo.getFilterCompiler() == null) {
navigationInfo.setFilterCompiler(addFilterCompiler(navigationInfo));
- }
-
- private List> createGroupBy(final Subquery> childQuery,
- final List orderByAttributes, final List selections) {
- if (!orderByAttributes.isEmpty()) {
-
- return selections.stream()
- .map(path -> mapOnToSelection(path, queryRoot, childQuery))
- .collect(toList()); // NOSONAR
}
- return emptyList();
}
- private List createOrderBy(final Subquery> childQuery) throws ODataApplicationException {
+ private List createOrderBy(final Subquery> childQuery, final Set> orderByPaths)
+ throws ODataApplicationException {
if (!hasRowLimit(childQuery)) {
final JPAOrderByBuilder orderByBuilder = new JPAOrderByBuilder(jpaEntity, queryRoot, cb, groups);
- return orderByBuilder.createOrderByList(joinTables, navigationInfo.getUriInfo(), navigationInfo.getPage());
+ return orderByBuilder.createOrderByList(joinTables, navigationInfo.getUriInfo(), navigationInfo.getPage(),
+ orderByPaths);
}
return emptyList();
}
@@ -234,18 +230,20 @@ JPAQueryPair createQueries(@Nullable final Subquery> childQuery) throws ODataA
void createRoots(final Subquery> childQuery, final JPAQueryPair queries,
final ProcessorSubquery> nextQuery) throws ODataApplicationException {
- if (hasRowLimit(childQuery))
+ if (hasRowLimit(childQuery)) {
this.queryRoot = nextQuery.from((ProcessorSubquery>) ((JPARowNumberFilterQuery) queries.inner())
.getSubQuery(childQuery, null, Collections.emptyList()));
- else
+ } else {
this.queryRoot = subQuery.from(this.jpaEntity.getTypeClass());
+ }
navigationInfo.setFromClause(queryRoot);
}
private Expression createWhere(final Subquery> childQuery) throws ODataApplicationException {
- if (hasRowLimit(childQuery))
+ if (hasRowLimit(childQuery)) {
return createWhereByRowNumber(queryRoot, navigationInfo);
+ }
return createWhereSubQuery(childQuery, false);
}
@@ -255,9 +253,10 @@ Expression createWhereSubQuery(@Nullable final Subquery> childQuery,
Expression whereCondition = createWhereByKey(queryRoot, this.keyPredicates, jpaEntity);
whereCondition = addWhereClause(whereCondition, createProtectionWhereForEntityType(claimsProvider, jpaEntity,
queryRoot));
- if (queryJoinTable != null)
+ if (queryJoinTable != null) {
whereCondition = addWhereClause(whereCondition, createWhereTableJoin(queryJoinTable, queryRoot, association,
useInverse));
+ }
if (childQuery != null) {
whereCondition = addWhereClause(whereCondition,
@@ -269,18 +268,22 @@ Expression createWhereSubQuery(@Nullable final Subquery> childQuery,
}
private Integer getSkipValue(@Nullable final Subquery> childQuery) {
- if (navigationInfo.getPage() != null)
- return navigationInfo.getPage().skip();
- if (navigationInfo.getUriInfo().getSkipOption() != null && childQuery == null)
+ if (navigationInfo.getPage() != null) {
+ return navigationInfo.getPage().skip() > 0 ? navigationInfo.getPage().skip() : null;
+ }
+ if (navigationInfo.getUriInfo().getSkipOption() != null && childQuery == null) {
return navigationInfo.getUriInfo().getSkipOption().getValue();
+ }
return null;
}
private Integer getTopValue(@Nullable final Subquery> childQuery) {
- if (navigationInfo.getPage() != null)
- return navigationInfo.getPage().top();
- if (navigationInfo.getUriInfo().getTopOption() != null && childQuery == null)
+ if (navigationInfo.getPage() != null) {
+ return navigationInfo.getPage().top() < Integer.MAX_VALUE ? navigationInfo.getPage().top() : null;
+ }
+ if (navigationInfo.getUriInfo().getTopOption() != null && childQuery == null) {
return navigationInfo.getUriInfo().getTopOption().getValue();
+ }
return null;
}
diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandItemInfoFactory.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandItemInfoFactory.java
index 774dcf749..07e90ac41 100644
--- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandItemInfoFactory.java
+++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandItemInfoFactory.java
@@ -65,6 +65,8 @@ public List buildExpandItemInfo(final JPAServiceDocument sd,
* ../Organizations('1')/Comment or
* ../CollectionDeeps?$select=FirstLevel/SecondLevel or
* ../CollectionDeeps/FirstLevel
+ * ../CollectionDeeps/FirstLevel/SecondLevel?$select=...,...
+ *
* @param sd
* @param uriResourceInfo
* @param optional
@@ -93,11 +95,12 @@ public List buildCollectionItemInfo(final JPAServiceDocum
// Moved
}
} else {
-
+ // Organizations('1')?$select=Comment
+ // Et/St?$select=Cp
if (SelectOptionUtil.selectAll(select)) {
// No navigation, extract all collection attributes
final JPAStructuredType st = (JPAStructuredType) pathInfo[ST_INDEX];
- final Set collectionProperties = new HashSet<>();
+ final Set collectionProperties = new HashSet<>();
for (final JPAPath path : st.getPathList()) {
final StringBuilder pathName = new StringBuilder(pathInfo[PATH_INDEX].toString());
for (final JPAElement pathElement : path.getPath()) {
@@ -107,27 +110,31 @@ public List buildCollectionItemInfo(final JPAServiceDocum
&& !attribute.isTransient()) {
final JPAPath collectionPath = ((JPAEntityType) pathInfo[ET_INDEX])
.getPath(pathName.deleteCharAt(pathName.length() - 1).toString());
- collectionProperties.add(collectionPath.getLeaf());
+ collectionProperties.add((JPACollectionAttribute) collectionPath.getLeaf());
}
break;
}
}
}
- for (final JPAElement pathElement : collectionProperties) {
+ for (final var pathElement : collectionProperties) {
final JPACollectionExpandWrapper item = new JPACollectionExpandWrapper((JPAEntityType) pathInfo[ET_INDEX],
uriResourceInfo);
- itemList.add(new JPACollectionItemInfo(sd, item, ((JPACollectionAttribute) pathElement)
- .asAssociation(), grandParentHops));
+ itemList.add(new JPACollectionItemInfo(sd, item, pathElement.asAssociation(), grandParentHops));
}
} else {
+ // Et?$select=Cp
+ // Et/St?$select=Cp,Cp
+ // Et/St/St?$select=Cp
+ // Et/St?$select=St/Cp
final JPAStructuredType st = (JPAStructuredType) pathInfo[ST_INDEX];
final Set selectOptions = getCollectionAttributesFromSelection(st, uriResourceInfo
.getSelectOption());
- for (final JPAPath path : selectOptions) {
+ final Map collectionPaths = Utility.determineAssociations(sd,
+ startResourceList, selectOptions);
+ for (final JPAAssociationPath path : collectionPaths.values()) {
final JPACollectionExpandWrapper item = new JPACollectionExpandWrapper((JPAEntityType) pathInfo[ET_INDEX],
- uriResourceInfo);
- itemList.add(new JPACollectionItemInfo(sd, item, ((JPACollectionAttribute) path.getLeaf())
- .asAssociation(), grandParentHops));
+ uriResourceInfo, path);
+ itemList.add(new JPACollectionItemInfo(sd, item, path, grandParentHops));
}
}
}
@@ -171,7 +178,7 @@ private Object[] determineNavigationElements(final JPAServiceDocument sd,
return result;
}
- protected Set getCollectionAttributesFromSelection(final JPAStructuredType jpaEntity,
+ private Set getCollectionAttributesFromSelection(final JPAStructuredType jpaEntity,
final SelectOption select) throws ODataApplicationException, ODataJPAModelException {
final Set collectionAttributes = new HashSet<>();
diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandJoinQuery.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandJoinQuery.java
index 6f83ac863..58e1de561 100644
--- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandJoinQuery.java
+++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandJoinQuery.java
@@ -8,9 +8,11 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.Set;
import jakarta.persistence.Tuple;
import jakarta.persistence.TypedQuery;
@@ -105,14 +107,16 @@ public JPAExpandQueryResult execute() throws ODataApplicationException {
}
private long determineTop() {
- if (uriResource.getTopOption() != null)
+ if (uriResource.getTopOption() != null) {
return uriResource.getTopOption().getValue();
+ }
return Long.MAX_VALUE;
}
private long determineSkip() {
- if (uriResource.getSkipOption() != null)
+ if (uriResource.getSkipOption() != null) {
return uriResource.getSkipOption().getValue();
+ }
return 0;
}
@@ -225,23 +229,27 @@ final Map count() throws ODataApplicationException {
private JPAQueryCreationResult createTupleQuery() throws ODataApplicationException, JPANoSelectionException {
try (JPARuntimeMeasurement measurement = debugger.newMeasurement(this, "createTupleQuery")) {
- final List orderByAttributes = extractOrderByNavigationAttributes(uriResource.getOrderByOption());
+ final List orderByAttributes = extractOrderByNavigationAttributes(uriResource
+ .getOrderByOption());
final SelectionPathInfo selectionPath = buildSelectionPathList(this.uriResource);
final Map> joinTables = createFromClause(orderByAttributes, selectionPath.joinedPersistent(),
cq, lastInfo);
// TODO handle Join Column is ignored
cq.multiselect(createSelectClause(joinTables, selectionPath.joinedPersistent(), target, groups)).distinct(true);
final jakarta.persistence.criteria.Expression whereClause = createWhere();
- if (whereClause != null)
+ if (whereClause != null) {
cq.where(whereClause);
+ }
+ final Set> orderByPaths = new HashSet<>();
final List orderBy = createOrderByJoinCondition(association);
orderBy.addAll(new JPAOrderByBuilder(jpaEntity, target, cb, groups).createOrderByList(joinTables, uriResource,
- page));
+ page, orderByPaths));
cq.orderBy(orderBy);
- if (!orderByAttributes.isEmpty())
- cq.groupBy(createGroupBy(joinTables, target, selectionPath.joinedPersistent()));
+ if (!orderByAttributes.isEmpty()) {
+ cq.groupBy(createGroupBy(joinTables, target, selectionPath.joinedPersistent(), orderByPaths));
+ }
final TypedQuery query = em.createQuery(cq);
diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandSubQuery.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandSubQuery.java
index b51c4c3dc..db76cc838 100644
--- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandSubQuery.java
+++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandSubQuery.java
@@ -12,10 +12,12 @@
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.Set;
import javax.annotation.Nonnull;
@@ -233,7 +235,7 @@ private void createFromClauseRoot(final CriteriaQuery> query, final HashMap createOrderBy(final Map> joinTables) throws ODataApplicationException {
+ private List createOrderBy(final Map> joinTables, Set> orderByPaths) throws ODataApplicationException {
if (association.hasJoinTable() && hasRowLimit(lastInfo)) {
try {
final List orders = new ArrayList<>();
@@ -251,7 +253,7 @@ private List createOrderBy(final Map> joinTables) thro
return orderByBuilder.createOrderByListAlias(joinTables, uriResource.getOrderByOption(), association);
} else {
final JPAOrderByBuilder orderByBuilder = new JPAOrderByBuilder(jpaEntity, root, cb, groups);
- return orderByBuilder.createOrderByList(joinTables, uriResource.getOrderByOption(), association);
+ return orderByBuilder.createOrderByList(joinTables, uriResource.getOrderByOption(), association, orderByPaths);
}
}
@@ -291,10 +293,12 @@ private JPAQueryPair createQueries(final SelectionPathInfo selectionPat
subQuery);
tupleQuery.where(createWhere(subQuery, lastInfo));
tupleQuery.multiselect(createSelectClause(joinTables, selectionPath.joinedPersistent(), groups));
- tupleQuery.orderBy(createOrderBy(joinTables));
+ final Set> orderByPaths = new HashSet<>();
+ tupleQuery.orderBy(createOrderBy(joinTables, orderByPaths));
tupleQuery.distinct(orderByAttributes.isEmpty());
- if (!orderByAttributes.isEmpty())
- cq.groupBy(createGroupBy(joinTables, target, selectionPath.joinedPersistent()));
+ if (!orderByAttributes.isEmpty()) {
+ cq.groupBy(createGroupBy(joinTables, target, selectionPath.joinedPersistent(),orderByPaths));
+ }
final TypedQuery query = em.createQuery(tupleQuery);
return new JPAQueryCreationResult(query, selectionPath);
}
diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAJoinQuery.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAJoinQuery.java
index 64d1e8f1b..20c213707 100644
--- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAJoinQuery.java
+++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAJoinQuery.java
@@ -7,14 +7,17 @@
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Optional;
+import java.util.Set;
import javax.annotation.Nonnull;
import jakarta.persistence.Tuple;
import jakarta.persistence.TypedQuery;
import jakarta.persistence.criteria.AbstractQuery;
+import jakarta.persistence.criteria.Path;
import org.apache.olingo.commons.api.ex.ODataException;
import org.apache.olingo.server.api.OData;
@@ -42,8 +45,9 @@ private static JPAEntityType determineTargetEntityType(final JPAODataRequestCont
final var resources = requestContext.getUriInfo().getUriResourceParts();
final var bindingTarget = Utility.determineBindingTarget(resources);
- if (bindingTarget instanceof EdmBoundCast)
+ if (bindingTarget instanceof EdmBoundCast) {
return requestContext.getEdmProvider().getServiceDocument().getEntity(bindingTarget.getEntityType());
+ }
return requestContext.getEdmProvider().getServiceDocument().getEntity(bindingTarget.getName());
}
@@ -87,13 +91,16 @@ public JPAConvertibleResult execute() throws ODataApplicationException {
.distinct(determineDistinct());
final var whereClause = createWhere();
- if (whereClause != null)
+ if (whereClause != null) {
cq.where(whereClause);
+ }
- cq.orderBy(createOrderByBuilder().createOrderByList(joinTables, uriResource, page));
+ final Set> orderByPaths = new HashSet<>();
+ cq.orderBy(createOrderByBuilder().createOrderByList(joinTables, uriResource, page, orderByPaths));
- if (!orderByNavigationAttributes.isEmpty())
- cq.groupBy(createGroupBy(joinTables, root, selectionPath.joinedPersistent()));
+ if (!orderByNavigationAttributes.isEmpty()) {
+ cq.groupBy(createGroupBy(joinTables, root, selectionPath.joinedPersistent(), orderByPaths));
+ }
final TypedQuery typedQuery = em.createQuery(cq);
addTopSkip(typedQuery);
@@ -111,8 +118,9 @@ public JPAConvertibleResult execute() throws ODataApplicationException {
}
private JPAOrderByBuilder createOrderByBuilder() throws ODataJPAQueryException {
- if (entitySet.isPresent())
+ if (entitySet.isPresent()) {
return new JPAOrderByBuilder(entitySet.get(), jpaEntity, target, cb, groups);
+ }
return new JPAOrderByBuilder(jpaEntity, target, cb, groups);
}
@@ -143,8 +151,9 @@ private boolean determineDistinct() {
private JPAConvertibleResult returnEmptyResult(final Collection selectionPath) {
if (lastInfo.getAssociationPath() != null
- && (lastInfo.getAssociationPath().getLeaf() instanceof JPACollectionAttribute))
+ && (lastInfo.getAssociationPath().getLeaf() instanceof JPACollectionAttribute)) {
return new JPACollectionQueryResult(jpaEntity, lastInfo.getAssociationPath(), selectionPath);
+ }
return new JPAExpandQueryResult(jpaEntity, selectionPath);
}
@@ -152,8 +161,9 @@ private JPAConvertibleResult returnResult(@Nonnull final Collection sel
final HashMap> result) throws ODataApplicationException {
final var odataEntityType = determineODataTargetEntityType(requestContext);
if (lastInfo.getAssociationPath() != null
- && (lastInfo.getAssociationPath().getLeaf() instanceof JPACollectionAttribute))
+ && (lastInfo.getAssociationPath().getLeaf() instanceof JPACollectionAttribute)) {
return new JPACollectionQueryResult(result, null, odataEntityType, lastInfo.getAssociationPath(), selectionPath);
+ }
return new JPAExpandQueryResult(result, Collections.emptyMap(), odataEntityType, selectionPath);
}
}
diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAOrderByBuilder.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAOrderByBuilder.java
index 969f6e0dc..b988ae3cb 100644
--- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAOrderByBuilder.java
+++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAOrderByBuilder.java
@@ -104,11 +104,12 @@ final class JPAOrderByBuilder {
* [...ComplexProperty,...PrimitiveProperty]
* .../Organizations?$orderby=Roles/$count --> one item, two resourcePaths [...NavigationProperty,...Count]
* .../Organizations?$orderby=Roles/$count desc,Address/Country asc -->two items
+ * .../AdminustrativeDivision?$orderby=Parent/DivisionCode
*