[OLINGO-663] Generic Precondition Required Handling

This commit is contained in:
Christian Amend 2015-06-01 13:52:52 +02:00
parent fd71b7ebdd
commit 53881c2acf
5 changed files with 272 additions and 121 deletions

View File

@ -18,6 +18,8 @@
*/
package org.apache.olingo.server.api;
import org.apache.olingo.commons.api.edm.EdmBindingTarget;
/**
* <p>Processors that would like to support etags for certain entity sets can implement this
* interface.</p>
@ -32,19 +34,41 @@ public interface CustomETagSupport {
* If this method returns true and an header is not specified we will return a "Precondition Required" response.
* Validation has to be performed inside the processor methods after the dispatching.
* If this method returns false and an header is specified we will ignore the header.
* @param entitySetName
* @param entitySetOrSingleton
* @return true if the entity set specified needs an if-match/if-none-match header
*/
boolean hasETag(String entitySetName);
boolean hasETag(EdmBindingTarget entitySetOrSingleton);
/**
* This method will be called for update requests which target a media entity value.
* If this method returns true and an header is not specified we will return a "Precondition Required" response.
* Validation has to be performed inside the processor methods after the dispatching.
* If this method returns false and an header is specified we will ignore the header.
* @param entitySetName
* @param entitySetOrSingleton
* @return true if the entity set specified needs an if-match/if-none-match header
*/
boolean hasMediaETag(String entitySetName);
boolean hasMediaETag(EdmBindingTarget entitySetOrSingleton);
/**
* Since the Olingo library cannot generate a metadata document etag in a generic way we call this method to retrieve
* an application specific etag for the metadata document. If this interface is registered applications can return an
* etag or null here to provide caching support for clients. If a client sends a GET request to the metadata document
* and this method delivers an etag we will match it to the request. If there has been no modification we will return
* a 304 NOT MODIFIED status code. If this interface is not registered or delivers null we just send back the usual
* metadata response.
* @return the application generated etag for the metadata document
*/
String getMetadataETag();
/**
* Since the Olingo library cannot generate a service document etag in a generic way we call this method to retrieve
* an application specific etag for the service document. If this interface is registered applications can return an
* etag or null here to provide caching support for clients. If a client sends a GET request to the service document
* and this method delivers an etag we will match it to the request. If there has been no modification we will return
* a 304 NOT MODIFIED status code. If this interface is not registered or delivers null we just send back the usual
* service document response.
* @return the application generated etag for the service document
*/
String getServiceDocumentETag();
}

View File

@ -24,7 +24,8 @@ public class PreconditionRequiredException extends ODataTranslatedException {
private static final long serialVersionUID = -8112658467394158700L;
public static enum MessageKeys implements MessageKey {
MISSING_HEADER;
MISSING_HEADER,
INVALID_URI;
@Override
public String getKey() {

View File

@ -18,9 +18,7 @@
*/
package org.apache.olingo.server.core;
import java.util.List;
import org.apache.olingo.commons.api.edm.EdmEntitySet;
import org.apache.olingo.commons.api.edm.EdmBindingTarget;
import org.apache.olingo.commons.api.edm.EdmFunctionImport;
import org.apache.olingo.commons.api.edm.EdmNavigationProperty;
import org.apache.olingo.server.api.CustomETagSupport;
@ -28,8 +26,8 @@ import org.apache.olingo.server.api.uri.UriInfo;
import org.apache.olingo.server.api.uri.UriResource;
import org.apache.olingo.server.api.uri.UriResourceEntitySet;
import org.apache.olingo.server.api.uri.UriResourceFunction;
import org.apache.olingo.server.api.uri.UriResourceKind;
import org.apache.olingo.server.api.uri.UriResourceNavigation;
import org.apache.olingo.server.api.uri.UriResourceSingleton;
public class PreconditionsValidator {
@ -48,10 +46,10 @@ public class PreconditionsValidator {
}
public void validatePreconditions(boolean isMediaValue) throws PreconditionRequiredException {
EdmEntitySet affectedEntitySet = extractInformation();
if (affectedEntitySet != null) {
if ((isMediaValue && customETagSupport.hasMediaETag(affectedEntitySet.getName())) ||
(!isMediaValue && customETagSupport.hasETag(affectedEntitySet.getName()))) {
EdmBindingTarget affectedEntitySetOrSingleton = extractInformation();
if (affectedEntitySetOrSingleton != null) {
if ((isMediaValue && customETagSupport.hasMediaETag(affectedEntitySetOrSingleton)) ||
(!isMediaValue && customETagSupport.hasETag(affectedEntitySetOrSingleton))) {
checkETagHeaderPresent();
}
}
@ -64,75 +62,69 @@ public class PreconditionsValidator {
}
}
private EdmEntitySet extractInformation() {
EdmEntitySet affectedEntitySet = null;
List<UriResource> uriResourceParts = uriInfo.getUriResourceParts();
if (uriResourceParts.size() > 0) {
UriResource uriResourcePart = uriResourceParts.get(uriResourceParts.size() - 1);
private EdmBindingTarget extractInformation() throws PreconditionRequiredException {
EdmBindingTarget lastFoundEntitySetOrSingleton = null;
int counter = 0;
for (UriResource uriResourcePart : uriInfo.getUriResourceParts()) {
switch (uriResourcePart.getKind()) {
case function:
lastFoundEntitySetOrSingleton = getEnitySetFromFunctionImport(uriResourcePart);
break;
case singleton:
lastFoundEntitySetOrSingleton = ((UriResourceSingleton) uriResourcePart).getSingleton();
break;
case entitySet:
affectedEntitySet = ((UriResourceEntitySet) uriResourcePart).getEntitySet();
lastFoundEntitySetOrSingleton = getEntitySet(uriResourcePart);
break;
case navigationProperty:
affectedEntitySet = getEntitySetFromBeginning();
lastFoundEntitySetOrSingleton = getEntitySetFromNavigation(lastFoundEntitySetOrSingleton, uriResourcePart);
break;
case value:
affectedEntitySet = getEntitySetOrNavigationEntitySet(uriResourceParts);
break;
case action:
affectedEntitySet = getEntitySetOrNavigationEntitySet(uriResourceParts);
// This should not be possible since the URI Parser validates this but to be sure we throw an exception.
if (counter != uriInfo.getUriResourceParts().size() - 1) {
throw new PreconditionRequiredException("$Value or Action must be the last segment in the URI.",
PreconditionRequiredException.MessageKeys.INVALID_URI);
}
break;
default:
// TODO: Cannot happen but should we throw an exception?
lastFoundEntitySetOrSingleton = null;
break;
}
} else {
// TODO: Cannot happen but should we throw an exception?
}
return affectedEntitySet;
}
private EdmEntitySet getEntitySetOrNavigationEntitySet(List<UriResource> uriResourceParts) {
EdmEntitySet affectedEntitySet = null;
UriResource previousResourcePart = uriResourceParts.get(uriResourceParts.size() - 2);
if (previousResourcePart.getKind() == UriResourceKind.entitySet) {
affectedEntitySet = ((UriResourceEntitySet) previousResourcePart).getEntitySet();
} else if (previousResourcePart.getKind() == UriResourceKind.navigationProperty) {
affectedEntitySet = getEntitySetFromBeginning();
}
return affectedEntitySet;
}
private EdmEntitySet getEntitySetFromBeginning() {
EdmEntitySet lastFoundES = null;
for (UriResource uriResourcePart : uriInfo.getUriResourceParts()) {
if (UriResourceKind.function == uriResourcePart.getKind()) {
EdmFunctionImport functionImport = ((UriResourceFunction) uriResourcePart).getFunctionImport();
if (functionImport != null && functionImport.getReturnedEntitySet() != null) {
lastFoundES = functionImport.getReturnedEntitySet();
} else {
lastFoundES = null;
break;
}
} else if (UriResourceKind.entitySet == uriResourcePart.getKind()) {
lastFoundES = ((UriResourceEntitySet) uriResourcePart).getEntitySet();
} else if (UriResourceKind.navigationProperty == uriResourcePart.getKind()) {
EdmNavigationProperty navProp = ((UriResourceNavigation) uriResourcePart).getProperty();
if (lastFoundES != null) {
lastFoundES = (EdmEntitySet) lastFoundES.getRelatedBindingTarget(navProp.getName());
if (lastFoundES == null) {
break;
}
}
} else if (UriResourceKind.value == uriResourcePart.getKind()
|| UriResourceKind.action == uriResourcePart.getKind()) {
// TODO: Should we validate that we are at the end of the resource path
break;
} else {
lastFoundES = null;
if (lastFoundEntitySetOrSingleton == null) {
// Once we loose track of the entity set there is no way to retrieve it.
break;
}
counter++;
}
return lastFoundES;
return lastFoundEntitySetOrSingleton;
}
private EdmBindingTarget getEnitySetFromFunctionImport(UriResource uriResourcePart) {
UriResourceFunction uriResourceFunction = (UriResourceFunction) uriResourcePart;
EdmFunctionImport functionImport = uriResourceFunction.getFunctionImport();
if (functionImport != null && functionImport.getReturnedEntitySet() != null
&& !uriResourceFunction.isCollection()) {
return functionImport.getReturnedEntitySet();
}
return null;
}
private EdmBindingTarget getEntitySet(UriResource uriResourcePart) {
UriResourceEntitySet uriResourceEntitySet = (UriResourceEntitySet) uriResourcePart;
if (!uriResourceEntitySet.isCollection()) {
return uriResourceEntitySet.getEntitySet();
}
return null;
}
private EdmBindingTarget getEntitySetFromNavigation(EdmBindingTarget lastFoundEntitySetOrSingleton,
UriResource uriResourcePart) {
UriResourceNavigation uriResourceNavigation = (UriResourceNavigation) uriResourcePart;
if (lastFoundEntitySetOrSingleton != null && !uriResourceNavigation.isCollection()) {
EdmNavigationProperty navProp = uriResourceNavigation.getProperty();
return lastFoundEntitySetOrSingleton.getRelatedBindingTarget(navProp.getName());
}
return null;
}
}

View File

@ -18,17 +18,28 @@
*/
package org.apache.olingo.server.tecsvc;
import org.apache.olingo.commons.api.edm.EdmBindingTarget;
import org.apache.olingo.server.api.CustomETagSupport;
public class ETagSupport implements CustomETagSupport {
@Override
public boolean hasETag(final String entitySetName) {
return entitySetName.equals("ESCompAllPrim");
public boolean hasETag(final EdmBindingTarget entitySetOrSingleton) {
return entitySetOrSingleton.getName().equals("ESCompAllPrim");
}
@Override
public boolean hasMediaETag(final String entitySetName) {
return entitySetName.equals("ESMedia");
public boolean hasMediaETag(final EdmBindingTarget entitySetOrSingleton) {
return entitySetOrSingleton.getName().equals("ESMedia");
}
@Override
public String getMetadataETag() {
return null;
}
@Override
public String getServiceDocumentETag() {
return null;
}
}

View File

@ -22,40 +22,35 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import org.apache.olingo.commons.api.edm.Edm;
import org.apache.olingo.commons.api.edm.EdmBindingTarget;
import org.apache.olingo.commons.api.http.HttpMethod;
import org.apache.olingo.commons.core.edm.EdmProviderImpl;
import org.apache.olingo.server.api.CustomETagSupport;
import org.apache.olingo.server.api.uri.UriInfo;
import org.apache.olingo.server.core.uri.parser.Parser;
import org.apache.olingo.server.core.uri.parser.UriParserException;
import org.apache.olingo.server.core.uri.parser.UriParserSemanticException;
import org.apache.olingo.server.core.uri.validator.UriValidator;
import org.apache.olingo.server.tecsvc.provider.EdmTechProvider;
import org.junit.Ignore;
import org.junit.Test;
public class PreconditionsValidatorTest {
// -------------- POSITIVE TESTS --------------------------------------------------------------------------------
@Test
public void simpleEntity() throws Exception {
UriInfo uriInfo = new Parser().parseUri("ESAllPrim(1)", null, null, getEdm());
new PreconditionsValidator(new ETagSupport(), uriInfo, "*", "*").validatePreconditions(false);
}
@Test
public void boundActionOnEsKeyNav() throws Exception {
UriInfo uriInfo =
new Parser().parseUri("ESKeyNav(1)/Namespace1_Alias.BAETTwoKeyNavRTETTwoKeyNav", null, null, getEdm());
new PreconditionsValidator(new ETagSupport(), uriInfo, "*", "*").validatePreconditions(false);
}
@Test
public void simpleEntityValue() throws Exception {
UriInfo uriInfo = new Parser().parseUri("ESMedia(1)/$value", null, null, getEdm());
new PreconditionsValidator(new ETagSupport(), uriInfo, "*", "*").validatePreconditions(true);
}
@Test
public void simpleEntityValueValidationNotActiveForMedia() throws Exception {
UriInfo uriInfo = new Parser().parseUri("ESMedia(1)/$value", null, null, getEdm());
new PreconditionsValidator(new ETagSupport(true, false), uriInfo, null, null).validatePreconditions(true);
}
@Test
public void EntityAndToOneNavigation() throws Exception {
UriInfo uriInfo = new Parser().parseUri("ESAllPrim(1)/NavPropertyETTwoPrimOne", null, null, getEdm());
@ -63,44 +58,164 @@ public class PreconditionsValidatorTest {
}
@Test
public void simpleEntityPreconditionsReqException() throws Exception {
UriInfo uriInfo = new Parser().parseUri("ESAllPrim(1)", null, null, getEdm());
try {
new PreconditionsValidator(new ETagSupport(), uriInfo, null, null).validatePreconditions(false);
fail("Expected a PreconditionRequiredException but was not thrown");
} catch (PreconditionRequiredException e) {
assertEquals(PreconditionRequiredException.MessageKeys.MISSING_HEADER, e.getMessageKey());
}
public void EntityAndToManyNavigationWithKey() throws Exception {
UriInfo uriInfo = new Parser().parseUri("ESAllPrim(1)/NavPropertyETTwoPrimMany(1)", null, null, getEdm());
new PreconditionsValidator(new ETagSupport("ESTwoPrim"), uriInfo, "*", "*").validatePreconditions(false);
}
@Test
public void boundActionOnEsKeyNavPreconditionsRequired() throws Exception {
public void EntityAndToOneNavigationValue() throws Exception {
UriInfo uriInfo = new Parser().parseUri("ESKeyNav(1)/NavPropertyETMediaOne/$value", null, null, getEdm());
new PreconditionsValidator(new ETagSupport("ESMedia"), uriInfo, "*", "*").validatePreconditions(true);
}
@Test
public void boundActionOnEsKeyNav() throws Exception {
UriInfo uriInfo =
new Parser().parseUri("ESKeyNav(1)/Namespace1_Alias.BAETTwoKeyNavRTETTwoKeyNav", null, null, getEdm());
try {
new PreconditionsValidator(new ETagSupport("ESKeyNav"), uriInfo, null, null).validatePreconditions(false);
fail("Expected a PreconditionRequiredException but was not thrown");
} catch (PreconditionRequiredException e) {
assertEquals(PreconditionRequiredException.MessageKeys.MISSING_HEADER, e.getMessageKey());
}
new PreconditionsValidator(new ETagSupport("ESKeyNav"), uriInfo, "*", "*").validatePreconditions(false);
}
@Test
public void simpleEntityValuePreconditionsRequired() throws Exception {
public void boundActionOnEsKeyNavWithNavigation() throws Exception {
UriInfo uriInfo =
new Parser().parseUri("ESKeyNav(1)/NavPropertyETKeyNavOne/Namespace1_Alias.BAETTwoKeyNavRTETTwoKeyNav", null,
null, getEdm());
new PreconditionsValidator(new ETagSupport("ESKeyNav"), uriInfo, "*", "*").validatePreconditions(false);
}
@Test
public void singleton() throws Exception {
UriInfo uriInfo = new Parser().parseUri("SI", null, null, getEdm());
new PreconditionsValidator(new ETagSupport("SI"), uriInfo, "*", "*").validatePreconditions(false);
}
@Test
public void singletonWithNavigation() throws Exception {
UriInfo uriInfo = new Parser().parseUri("SINav/NavPropertyETKeyNavOne", null, null, getEdm());
new PreconditionsValidator(new ETagSupport("ESKeyNav"), uriInfo, "*", "*").validatePreconditions(false);
}
@Test
public void singletonWithNavigationValue() throws Exception {
UriInfo uriInfo =
new Parser().parseUri("SINav/NavPropertyETKeyNavOne/NavPropertyETMediaOne/$value", null, null, getEdm());
new PreconditionsValidator(new ETagSupport("ESMedia"), uriInfo, "*", "*").validatePreconditions(false);
}
@Test
public void singletonWithAction() throws Exception {
UriInfo uriInfo = new Parser().parseUri("SINav/Namespace1_Alias.BAETTwoKeyNavRTETTwoKeyNav", null, null, getEdm());
new PreconditionsValidator(new ETagSupport("SINav"), uriInfo, "*", "*").validatePreconditions(false);
}
@Test
public void singletonWithActionAndNavigation() throws Exception {
UriInfo uriInfo =
new Parser().parseUri("SINav/NavPropertyETKeyNavOne/Namespace1_Alias.BAETTwoKeyNavRTETTwoKeyNav", null, null,
getEdm());
new PreconditionsValidator(new ETagSupport("ESKeyNav"), uriInfo, "*", "*").validatePreconditions(false);
}
@Test
public void simpleEntityValueValidationNotActiveForMedia() throws Exception {
UriInfo uriInfo = new Parser().parseUri("ESMedia(1)/$value", null, null, getEdm());
try {
new PreconditionsValidator(new ETagSupport(), uriInfo, null, null).validatePreconditions(true);
fail("Expected a PreconditionRequiredException but was not thrown");
} catch (PreconditionRequiredException e) {
assertEquals(PreconditionRequiredException.MessageKeys.MISSING_HEADER, e.getMessageKey());
}
new PreconditionsValidator(new ETagSupport(true, false), uriInfo, null, null).validatePreconditions(true);
}
// -------------- IGNORE VALIDATION TESTS -----------------------------------------------------------------------
@Test
public void entitySetMustNotLeadToException() throws Exception {
UriInfo uriInfo = new Parser().parseUri("ESAllPrim", null, null, getEdm());
new PreconditionsValidator(new ETagSupport(), uriInfo, null, null).validatePreconditions(false);
}
@Test
public void EntityAndToOneNavigationPreconditionsRequired() throws Exception {
UriInfo uriInfo = new Parser().parseUri("ESAllPrim(1)/NavPropertyETTwoPrimOne", null, null, getEdm());
public void propertyMustNotLeadToException() throws Exception {
UriInfo uriInfo = new Parser().parseUri("ESAllPrim(1)/PropertyInt16", null, null, getEdm());
new PreconditionsValidator(new ETagSupport(), uriInfo, null, null).validatePreconditions(false);
}
@Test
public void propertyValueMustNotLeadToException() throws Exception {
UriInfo uriInfo = new Parser().parseUri("ESAllPrim(1)/PropertyInt16/$value", null, null, getEdm());
new PreconditionsValidator(new ETagSupport(), uriInfo, null, null).validatePreconditions(true);
}
@Test
public void navigationToManyMustNotLeadToException() throws Exception {
UriInfo uriInfo = new Parser().parseUri("ESAllPrim(1)/NavPropertyETTwoPrimMany", null, null, getEdm());
new PreconditionsValidator(new ETagSupport(), uriInfo, null, null).validatePreconditions(false);
}
@Test
public void navigationOnPropertyMustNotLeadToException() throws Exception {
UriInfo uriInfo = new Parser().parseUri("ESAllPrim(1)/NavPropertyETTwoPrimOne/PropertyInt16", null, null, getEdm());
new PreconditionsValidator(new ETagSupport(), uriInfo, null, null).validatePreconditions(false);
}
@Test
public void navigationToManyOnActionMustNotLeadToException() throws Exception {
UriInfo uriInfo =
new Parser().parseUri("ESTwoPrim(1)/NavPropertyETAllPrimMany/Namespace1_Alias.BAESAllPrimRTETAllPrim", null,
null, getEdm());
new PreconditionsValidator(new ETagSupport(), uriInfo, null, null).validatePreconditions(false);
}
@Test
public void navigationWithoutBindingMustNotLeadToAnException() throws Exception {
UriInfo uriInfo =
new Parser()
.parseUri(
"ESTwoBaseTwoKeyNav(PropertyInt16=1,PropertyString='test')"
+ "/NavPropertyETBaseTwoKeyNavMany(PropertyInt16=1,PropertyString='test')",
null, null, getEdm());
new PreconditionsValidator(new ETagSupport(), uriInfo, null, null).validatePreconditions(false);
}
// -------------- NEGATIVE TESTS --------------------------------------------------------------------------------
@Test
public void positiveTestsMustLeadToAnExceptionIfNoHeaderIsPresent() throws Exception {
runException("ESAllPrim(1)", null);
runException("ESMedia(1)/$value", null);
runException("ESAllPrim(1)/NavPropertyETTwoPrimOne", null);
runException("ESAllPrim(1)/NavPropertyETTwoPrimMany(1)", null);
runException("ESKeyNav(1)/NavPropertyETMediaOne/$value", null);
runException("ESKeyNav(1)/Namespace1_Alias.BAETTwoKeyNavRTETTwoKeyNav", null);
runException("ESKeyNav(1)/NavPropertyETKeyNavOne/Namespace1_Alias.BAETTwoKeyNavRTETTwoKeyNav", null);
runException("SI", null);
runException("SINav/NavPropertyETKeyNavOne", null);
runException("SINav/NavPropertyETKeyNavOne/NavPropertyETMediaOne/$value", null);
runException("SINav/Namespace1_Alias.BAETTwoKeyNavRTETTwoKeyNav", null);
runException("SINav/NavPropertyETKeyNavOne/Namespace1_Alias.BAETTwoKeyNavRTETTwoKeyNav", null);
}
@Ignore
@Test
public void resourceSegmentAfterActionMustLeadToUriParserException() throws Exception {
//TODO: Check with URI Parser
UriInfo uriInfo =
new Parser().parseUri("ESKeyNav(1)/Namespace1_Alias.BAETTwoKeyNavRTETTwoKeyNav/PropertyInt16", null, null,
getEdm());
new UriValidator().validate(uriInfo, HttpMethod.GET);
new PreconditionsValidator(new ETagSupport("ESKeyNav"), uriInfo, "*", "*").validatePreconditions(false);
}
@Test(expected = UriParserSemanticException.class)
public void valueMustBeLastSegment() throws Exception {
new Parser().parseUri("ESMedia(1)/$value/PropertyInt16", null, null, getEdm());
}
private void runException(String uri, String expectedEntitySet) throws UriParserException {
UriInfo uriInfo = new Parser().parseUri(uri, null, null, getEdm());
try {
new PreconditionsValidator(new ETagSupport(), uriInfo, null, null).validatePreconditions(false);
CustomETagSupport etagSupport =
expectedEntitySet == null ? new ETagSupport() : new ETagSupport(expectedEntitySet);
boolean isMedia = uri.endsWith("$value");
new PreconditionsValidator(etagSupport, uriInfo, null, null).validatePreconditions(isMedia);
fail("Expected a PreconditionRequiredException but was not thrown");
} catch (PreconditionRequiredException e) {
assertEquals(PreconditionRequiredException.MessageKeys.MISSING_HEADER, e.getMessageKey());
@ -117,33 +232,41 @@ public class PreconditionsValidatorTest {
private boolean mediaETag = true;
private String entitySetName;
public ETagSupport() {
}
public ETagSupport() {}
public ETagSupport(String entitySetName) {
this.entitySetName = entitySetName;
}
}
public ETagSupport(boolean eTag, boolean mediaETag) {
this.eTag = eTag;
this.mediaETag = mediaETag;
}
@Override
public boolean hasETag(String entitySetName) {
if(this.entitySetName != null){
assertEquals(this.entitySetName, entitySetName);
public boolean hasETag(EdmBindingTarget entitySetOrSingeton) {
if (this.entitySetName != null) {
assertEquals(this.entitySetName, entitySetOrSingeton.getName());
}
return eTag;
}
@Override
public boolean hasMediaETag(String entitySetName) {
if(this.entitySetName != null){
assertEquals(this.entitySetName, entitySetName);
public boolean hasMediaETag(EdmBindingTarget entitySetOrSingelton) {
if (this.entitySetName != null) {
assertEquals(this.entitySetName, entitySetOrSingelton.getName());
}
return mediaETag;
}
@Override
public String getMetadataETag() {
return null;
}
@Override
public String getServiceDocumentETag() {
return null;
}
}
}