Skip to content

Commit fb55a09

Browse files
authored
[OLINGO-1630] Add support for Segment as Path from Odata v4.01
Add support for Segment as Path from Odata v4.01
2 parents 20b0d7a + b483f5a commit fb55a09

File tree

11 files changed

+243
-13
lines changed

11 files changed

+243
-13
lines changed

lib/commons-api/src/main/java/org/apache/olingo/commons/api/edm/EdmEntitySet.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,8 @@ public interface EdmEntitySet extends EdmBindingTarget {
3131
*/
3232
boolean isIncludeInServiceDocument();
3333

34+
/**
35+
* @return true if the entity set allows for the next segment to be the key.
36+
*/
37+
boolean isKeyAsSegmentAllowed();
3438
}

lib/commons-api/src/main/java/org/apache/olingo/commons/api/edm/EdmNavigationProperty.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,9 @@ public interface EdmNavigationProperty extends EdmElement, EdmAnnotatable {
6060

6161
EdmOnDelete getOnDelete();
6262

63+
/**
64+
* @return true if the entity set allows for the next segment to be the key.
65+
*/
66+
boolean isKeyAsSegmentAllowed();
67+
6368
}

lib/commons-api/src/main/java/org/apache/olingo/commons/api/edm/provider/CsdlEntitySet.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ public class CsdlEntitySet extends CsdlBindingTarget {
3030
// Default for EntitySets is true
3131
private boolean includeInServiceDocument = true;
3232

33+
// Default for EntitySets is false
34+
private boolean keyAsSegmentAllowed = false;
35+
3336
@Override
3437
public CsdlEntitySet setName(final String name) {
3538
this.name = name;
@@ -81,7 +84,27 @@ public CsdlEntitySet setIncludeInServiceDocument(final boolean includeInServiceD
8184
this.includeInServiceDocument = includeInServiceDocument;
8285
return this;
8386
}
84-
87+
88+
/**
89+
* Is this entity set allowed to be followed by a path variable.
90+
*
91+
* @return the boolean
92+
*/
93+
public boolean isKeyAsSegmentAllowed() {
94+
return keyAsSegmentAllowed;
95+
}
96+
97+
/**
98+
* Sets the path variable allowed parameter.
99+
*
100+
* @param keyAsSegmentAllowed whether path variables are allowed
101+
* @return EntitySet with path variable boolean set.
102+
*/
103+
public CsdlEntitySet setKeyAsSegmentAllowed(boolean keyAsSegmentAllowed) {
104+
this.keyAsSegmentAllowed = keyAsSegmentAllowed;
105+
return this;
106+
}
107+
85108
@Override
86109
public CsdlEntitySet setTitle(String title) {
87110
super.setTitle(title);

lib/commons-api/src/main/java/org/apache/olingo/commons/api/edm/provider/CsdlNavigationProperty.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ public class CsdlNavigationProperty extends CsdlAbstractEdmItem implements CsdlN
4747

4848
private List<CsdlAnnotation> annotations = new ArrayList<CsdlAnnotation>();
4949

50+
// Default for EntitySets is false
51+
private boolean keyAsSegmentAllowed = false;
52+
5053
@Override
5154
public String getName() {
5255
return name;
@@ -241,4 +244,24 @@ public CsdlNavigationProperty setAnnotations(final List<CsdlAnnotation> annotati
241244
this.annotations = annotations;
242245
return this;
243246
}
247+
248+
/**
249+
* Is this entity set allowed to be followed by a path variable.
250+
*
251+
* @return the boolean
252+
*/
253+
public boolean isKeyAsSegmentAllowed() {
254+
return keyAsSegmentAllowed;
255+
}
256+
257+
/**
258+
* Sets the path variable allowed parameter.
259+
*
260+
* @param keyAsSegmentAllowed whether path variables are allowed
261+
* @return EntitySet with path variable boolean set.
262+
*/
263+
public CsdlNavigationProperty setKeyAsSegmentAllowed(boolean keyAsSegmentAllowed) {
264+
this.keyAsSegmentAllowed = keyAsSegmentAllowed;
265+
return this;
266+
}
244267
}

lib/commons-core/src/main/java/org/apache/olingo/commons/core/edm/EdmEntitySetImpl.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,8 @@ public boolean isIncludeInServiceDocument() {
3737
return entitySet.isIncludeInServiceDocument();
3838
}
3939

40+
@Override
41+
public boolean isKeyAsSegmentAllowed() {
42+
return entitySet.isKeyAsSegmentAllowed();
43+
}
4044
}

lib/commons-core/src/main/java/org/apache/olingo/commons/core/edm/EdmNavigationPropertyImpl.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,4 +127,9 @@ public EdmOnDelete getOnDelete() {
127127
CsdlOnDelete csdlOnDelete = navigationProperty.getOnDelete();
128128
return csdlOnDelete != null ? new EdmOnDeleteImpl(edm, csdlOnDelete) : null;
129129
}
130+
131+
@Override
132+
public boolean isKeyAsSegmentAllowed() {
133+
return navigationProperty.isKeyAsSegmentAllowed();
134+
}
130135
}

lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ResourcePathParser.java

Lines changed: 80 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@
3636
import org.apache.olingo.commons.api.edm.EdmStructuredType;
3737
import org.apache.olingo.commons.api.edm.EdmType;
3838
import org.apache.olingo.commons.api.edm.FullQualifiedName;
39+
import org.apache.olingo.commons.api.edm.EdmKeyPropertyRef;
3940
import org.apache.olingo.commons.api.edm.constants.EdmTypeKind;
41+
import org.apache.olingo.commons.core.edm.primitivetype.EdmString;
4042
import org.apache.olingo.server.api.uri.UriParameter;
4143
import org.apache.olingo.server.api.uri.UriResource;
4244
import org.apache.olingo.server.api.uri.UriResourceEntitySet;
@@ -56,6 +58,7 @@
5658
import org.apache.olingo.server.core.uri.UriResourceTypedImpl;
5759
import org.apache.olingo.server.core.uri.UriResourceValueImpl;
5860
import org.apache.olingo.server.core.uri.UriResourceWithKeysImpl;
61+
import org.apache.olingo.server.core.uri.UriParameterImpl;
5962
import org.apache.olingo.server.core.uri.parser.UriTokenizer.TokenKind;
6063
import org.apache.olingo.server.core.uri.validator.UriValidationException;
6164

@@ -89,16 +92,35 @@ public UriResource parsePathSegment(final String pathSegment, UriResource previo
8992
}
9093

9194
} else {
92-
if (tokenizer.next(TokenKind.REF)) {
93-
return ref(previous);
94-
} else if (tokenizer.next(TokenKind.VALUE)) {
95-
return value(previous);
96-
} else if (tokenizer.next(TokenKind.COUNT)) {
97-
return count(previous);
98-
} else if (tokenizer.next(TokenKind.QualifiedName)) {
99-
return boundOperationOrTypeCast(previous);
100-
} else if (tokenizer.next(TokenKind.ODataIdentifier)) {
101-
return navigationOrProperty(previous);
95+
try {
96+
if (tokenizer.next(TokenKind.REF)) {
97+
return ref(previous);
98+
} else if (tokenizer.next(TokenKind.VALUE)) {
99+
return value(previous);
100+
} else if (tokenizer.next(TokenKind.COUNT)) {
101+
return count(previous);
102+
} else if (tokenizer.next(TokenKind.QualifiedName)) {
103+
return boundOperationOrTypeCast(previous);
104+
} else if (tokenizer.next(TokenKind.ODataIdentifier)) {
105+
return navigationOrProperty(previous);
106+
} else if (canParseKeyFromSegment(pathSegment, previous)) {
107+
/*
108+
* This should only be reached if the path segment is an integer. If it can be parsed as a key segment,
109+
* null is returned so that this portion of the path is skipped.
110+
*/
111+
return null;
112+
}
113+
} catch (UriParserException e) {
114+
/*
115+
* This exception will be caught if the path segment is a string. It will first try to be parsed as a navigation
116+
* or property. That will fail if the path segment is not part of the schema. If it can be parsed as a key
117+
* segment, null is returned so that this portion of the path is skipped. If it can not be parsed as a key
118+
* segment, then the original exception is re-thrown.
119+
*/
120+
if (canParseKeyFromSegment(pathSegment,previous)) {
121+
return null;
122+
}
123+
throw e;
102124
}
103125
}
104126

@@ -141,6 +163,30 @@ public List<String> parseCrossjoinSegment(final String pathSegment) throws UriPa
141163
return entitySetNames;
142164
}
143165

166+
/*
167+
* This logic is to handle our use case of supporting keys as segments.
168+
* If all the existing parsing fails, then we check if the previous segment is an entity set, or navigation.
169+
* If it is, and it has the flag set allow keys as segments, then we check if the entity set already has
170+
* the key parameter set. If it does, then we return the original parse exception (ie /entitySet('key')/un-parseable).
171+
* If there is no key set, then we pass the current segment back to the previous segment as the key.
172+
* With this logic, /entitySet('key') and /entitySet/key should be equivalent.
173+
*/
174+
private boolean canParseKeyFromSegment(final String pathSegment, UriResource previous) {
175+
if (previous instanceof UriResourceEntitySetImpl) {
176+
UriResourceEntitySetImpl entitySet = (UriResourceEntitySetImpl) previous;
177+
if (entitySet.getEntitySet().isKeyAsSegmentAllowed()) {
178+
return didSetKeySegmentAsKey(pathSegment, entitySet, entitySet.getEntitySet().getEntityType());
179+
}
180+
} else if (previous instanceof UriResourceNavigationPropertyImpl) {
181+
UriResourceNavigationPropertyImpl navProp = (UriResourceNavigationPropertyImpl) previous;
182+
EdmNavigationProperty edmNavProperty = navProp.getProperty();
183+
if (edmNavProperty.isKeyAsSegmentAllowed()) {
184+
return didSetKeySegmentAsKey(pathSegment, navProp, edmNavProperty.getType());
185+
}
186+
}
187+
return false;
188+
}
189+
144190
private UriResource ref(final UriResource previous) throws UriParserException {
145191
ParserHelper.requireTokenEnd(tokenizer);
146192
requireTyped(previous, "$ref");
@@ -423,4 +469,28 @@ private UriResource functionCall(final EdmFunctionImport edmFunctionImport,
423469
ParserHelper.requireTokenEnd(tokenizer);
424470
return resource;
425471
}
472+
473+
private boolean didSetKeySegmentAsKey(
474+
final String pathSegment,
475+
UriResourceWithKeysImpl resourceWithKeys,
476+
EdmEntityType entityType) {
477+
List<EdmKeyPropertyRef> keys = entityType.getKeyPropertyRefs();
478+
if (keys != null && keys.size() == 1) {
479+
// Currently only supports types with a single key.
480+
EdmKeyPropertyRef propertyRef = keys.get(0);
481+
String keyName = propertyRef.getName();
482+
List<UriParameter> parameterList = new ArrayList<>(resourceWithKeys.getKeyPredicates());
483+
if (parameterList.stream().noneMatch(u -> keyName.equals(u.getName()))) {
484+
String value = pathSegment;
485+
if (propertyRef.getProperty().getType() instanceof EdmString) {
486+
value = "'" + value + "'";
487+
}
488+
parameterList.add(new UriParameterImpl().setName(keyName).setText(value));
489+
resourceWithKeys.setKeyPredicates(parameterList);
490+
return true;
491+
}
492+
}
493+
494+
return false;
495+
}
426496
}

lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/provider/ContainerProvider.java

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -730,7 +730,31 @@ public CsdlEntitySet getEntitySet(final FullQualifiedName entityContainer, final
730730
return new CsdlEntitySet()
731731
.setName("ESStreamOnComplexProp")
732732
.setType(EntityTypeProvider.nameETStreamOnComplexProp);
733-
}
733+
} else if (name.equals("ESKeyAsSegmentString")) {
734+
return new CsdlEntitySet()
735+
.setName("ESKeyAsSegmentString")
736+
.setType(EntityTypeProvider.nameETKeyAsSegmentString)
737+
.setKeyAsSegmentAllowed(true);
738+
} else if (name.equals("ESKeyAsSegmentInt")) {
739+
return new CsdlEntitySet()
740+
.setName("ESKeyAsSegmentInt")
741+
.setType(EntityTypeProvider.nameETKeyAsSegmentInt)
742+
.setKeyAsSegmentAllowed(true);
743+
} else if (name.equals("ESComplexKeyAsSegment")) {
744+
return new CsdlEntitySet()
745+
.setName("ESComplexKeyAsSegment")
746+
.setType(EntityTypeProvider.nameETComplexKeyAsSegment)
747+
.setKeyAsSegmentAllowed(true);
748+
} else if (name.equals("ESKeyAsSegmentStringNavKeyAsSegment")) {
749+
return new CsdlEntitySet()
750+
.setName("ESKeyAsSegmentStringNavKeyAsSegment")
751+
.setType(EntityTypeProvider.nameETKeyAsSegmentStringNavKeyAsSegment)
752+
.setKeyAsSegmentAllowed(true)
753+
.setNavigationPropertyBindings(Arrays.asList(new CsdlNavigationPropertyBinding()
754+
.setPath(PropertyProvider.navPropertyKeyAsSegment.getName())
755+
.setTarget("ESKeyAsSegmentString")
756+
));
757+
}
734758
}
735759
return null;
736760
}

lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/provider/EntityTypeProvider.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,18 @@ public class EntityTypeProvider {
100100

101101
public static final FullQualifiedName nameETStreamOnComplexProp =
102102
new FullQualifiedName(SchemaProvider.NAMESPACE, "ETStreamOnComplexProp");
103+
104+
public static final FullQualifiedName nameETKeyAsSegmentString =
105+
new FullQualifiedName(SchemaProvider.NAMESPACE, "ETKeyAsSegmentString");
106+
107+
public static final FullQualifiedName nameETKeyAsSegmentInt =
108+
new FullQualifiedName(SchemaProvider.NAMESPACE, "ETKeyAsSegmentInt");
109+
110+
public static final FullQualifiedName nameETComplexKeyAsSegment =
111+
new FullQualifiedName(SchemaProvider.NAMESPACE, "ETComplexKeyAsSegment");
112+
113+
public static final FullQualifiedName nameETKeyAsSegmentStringNavKeyAsSegment =
114+
new FullQualifiedName(SchemaProvider.NAMESPACE, "ETKeyAsSegmentStringNavKeyAsSegment");
103115

104116
public CsdlEntityType getEntityType(final FullQualifiedName entityTypeName) throws ODataException {
105117
if(entityTypeName.equals(nameETAllPrimDefaultValues)){
@@ -574,6 +586,37 @@ public CsdlEntityType getEntityType(final FullQualifiedName entityTypeName) thro
574586
PropertyProvider.propertyInt32, PropertyProvider.propertyEntityStream,
575587
PropertyProvider.propertyCompWithStream_CTWithStreamProp
576588
));
589+
} else if (entityTypeName.equals(nameETKeyAsSegmentString)) {
590+
return new CsdlEntityType()
591+
.setName("ETKeyAsSegmentString")
592+
.setKey(Arrays.asList(
593+
new CsdlPropertyRef().setName("PropertyString")))
594+
.setProperties(Arrays.asList(
595+
PropertyProvider.propertyString_NotNullable));
596+
} else if (entityTypeName.equals(nameETKeyAsSegmentInt)) {
597+
return new CsdlEntityType()
598+
.setName("ETKeyAsSegmentInt")
599+
.setKey(Arrays.asList(
600+
new CsdlPropertyRef().setName("PropertyInt16")))
601+
.setProperties(Arrays.asList(
602+
PropertyProvider.propertyInt16_NotNullable));
603+
} else if (entityTypeName.equals(nameETComplexKeyAsSegment)) {
604+
return new CsdlEntityType()
605+
.setName("ETComplexKeyAsSegment")
606+
.setKey(Arrays.asList(
607+
new CsdlPropertyRef().setName("PropertyString"),
608+
new CsdlPropertyRef().setName("PropertyInt16")))
609+
.setProperties(Arrays.asList(
610+
PropertyProvider.propertyString_NotNullable,
611+
PropertyProvider.propertyInt16_NotNullable));
612+
} else if(entityTypeName.equals(nameETKeyAsSegmentStringNavKeyAsSegment)) {
613+
return new CsdlEntityType()
614+
.setName(nameETKeyAsSegmentStringNavKeyAsSegment.getName())
615+
.setKey(Arrays.asList(
616+
new CsdlPropertyRef().setName("PropertyString")))
617+
.setProperties(Arrays.asList(
618+
PropertyProvider.propertyString_NotNullable))
619+
.setNavigationProperties(Arrays.asList(PropertyProvider.navPropertyKeyAsSegment));
577620
}
578621
return null;
579622
}

lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/provider/PropertyProvider.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1051,4 +1051,9 @@ public class PropertyProvider {
10511051
.setName("NavPropertyETStreamOnComplexPropMany")
10521052
.setType(EntityTypeProvider.nameETStream)
10531053
.setCollection(true);
1054-
}
1054+
1055+
public static final CsdlNavigationProperty navPropertyKeyAsSegment = new CsdlNavigationProperty()
1056+
.setName("NavPropertyKeyAsSegment")
1057+
.setType(EntityTypeProvider.nameETKeyAsSegmentStringNavKeyAsSegment)
1058+
.setKeyAsSegmentAllowed(true);
1059+
}

0 commit comments

Comments
 (0)