This commit is contained in:
fmartelli 2014-05-12 11:55:39 +02:00
commit 8bd9e0ca97
10 changed files with 403 additions and 32 deletions

View File

@ -1,17 +1,35 @@
{
"odata.error":
{
"code": "",
"message":
{
"lang": "en-US",
"value": "Bad request."
},
"innererror":
{
"message": "Bad request.",
"type": "Microsoft.Data.OData.BadRequest",
"stacktrace": " at Microsoft.Data.OData.MediaTypeUtils.GetContentTypeFromSettings...."
}
}
}
"error": {
"code": "400",
"message": "Bad request.",
"target": "query",
"details": [
{
"code": "400",
"target": "$search" ,
"message": "Microsoft.Data.OData.BadRequest"
}
],
"innererror": {
"trace": ["at Microsoft.Data.OData.MediaTypeUtils.GetContentTypeFromSettings....","callmethod2 etc"],
"context": {"key1":"for debug deployment only"}
}
}
}

View File

@ -1,11 +1,35 @@
{
"odata.error":
{
"code": "",
"message":
{
"lang": "en-US",
"value": "Resource not found for the segment 'Customer'."
}
}
}
"error": {
"code": "501",
"message": "Unsupported functionality",
"target": "query",
"details": [
{
"code": "301",
"target": "$search",
"message": "$search query option not supported"
}
],
"innererror": {
"trace": ["callmethod1 etc","callmethod2 etc"],
"context": {"key1":"for debug deployment only"}
}
}
}

View File

@ -0,0 +1,76 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.olingo.fit.v4;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.net.URI;
import java.util.Dictionary;
import org.apache.olingo.client.api.communication.ODataClientErrorException;
import org.apache.olingo.client.api.communication.request.retrieve.ODataEntityRequest;
import org.apache.olingo.commons.api.domain.ODataError;
import org.apache.olingo.commons.api.domain.ODataErrorDetail;
import org.apache.olingo.commons.api.domain.v4.ODataEntity;
import org.apache.olingo.commons.api.format.ODataPubFormat;
import org.junit.Test;
public class ErrorResponseTestITCase extends AbstractTestITCase {
@Test
public void jsonError() {
ODataPubFormat format = ODataPubFormat.JSON;
final URI readURI = getClient().getURIBuilder(testStaticServiceRootURL)
.appendEntitySetSegment("Customers").appendKeySegment(32)
.build();
final ODataEntityRequest<ODataEntity> req = getClient()
.getRetrieveRequestFactory().getEntityRequest(readURI);
try {
final ODataEntity read = read(format, readURI);
} catch (Exception ex) {
ODataError err = ((ODataClientErrorException) ex).getODataError();
// verify details
ODataErrorDetail detail = (ODataErrorDetail) err.getDetails()
.get(0);
assertEquals("Code should be correct", "301", detail.getCode());
assertEquals("Target should be correct", "$search",
detail.getTarget());
assertEquals("Message should be correct",
"$search query option not supported", detail.getMessage());
// verify inner error dictionary
Dictionary<String, Object> innerErr = err.getInnerError();
assertEquals("innerError dictionary size should be correct", 2,
innerErr.size());
assertEquals("innerError['context'] should be correct",
"{\"key1\":\"for debug deployment only\"}",
innerErr.get("context"));
assertEquals("innerError['trace'] should be correct",
"[\"callmethod1 etc\",\"callmethod2 etc\"]",
innerErr.get("trace"));
return;
}
assertNotNull("should have got exception", null);
}
}

View File

@ -245,6 +245,10 @@ public interface Constants {
public static final String ERROR_TARGET = "target";
public static final String ERROR_DETAILS = "details";
public static final String ERROR_INNERERROR= "innererror";
// canonical functions to be applied via dynamic annotation <tt>Apply</tt>
public static final String CANONICAL_FUNCTION_CONCAT = "odata.concat";

View File

@ -18,6 +18,9 @@
*/
package org.apache.olingo.commons.api.domain;
import java.util.Dictionary;
import java.util.List;
/**
* OData error.
*/
@ -43,5 +46,19 @@ public interface ODataError {
* @return error message.
*/
String getTarget();
/**
* Gets error details.
*
* @return ODataErrorDetail list.
*/
List<ODataErrorDetail> getDetails();
/**
* Gets server defined key-value pairs for debug environment only.
*
* @return a Dictionary representing server defined object.
*/
Dictionary<String, Object> getInnerError();
}

View File

@ -0,0 +1,48 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.olingo.commons.api.domain;
/**
* OData details for example - { "error": {..., "details":[
* {"code": "301","target": "$search" ,"message": "$search query option not supported"}
* ],...}}
*/
public interface ODataErrorDetail {
/**
* Gets error code.
*
* @return error code.
*/
String getCode();
/**
* Gets error message.
*
* @return error message.
*/
String getMessage();
/**
* Gets error target.
*
* @return error message.
*/
String getTarget();
}

View File

@ -18,9 +18,12 @@
*/
package org.apache.olingo.commons.core.data;
import org.apache.olingo.commons.api.domain.ODataError;
import java.util.Dictionary;
import java.util.List;
import org.apache.olingo.commons.api.domain.ODataError;
import org.apache.olingo.commons.api.domain.ODataErrorDetail;
// TODO p2 supports V4:
// {
// "error": {
// "code": "501",
@ -29,10 +32,10 @@ import org.apache.olingo.commons.api.domain.ODataError;
// "details": [
// {
// "code": "301",
// "target": "$search"
// "message": "$search query option not supported",
// "target": "$search",
// "message": "$search query option not supported"
// }
// ]
// ],
// "innererror": {
// "trace": [...],
// "context": {...}
@ -47,6 +50,10 @@ public abstract class AbstractODataError implements ODataError {
private String target;
private List<ODataErrorDetail> details;
private Dictionary<String,Object> innerError;
@Override
public String getCode() {
return code;
@ -74,4 +81,21 @@ public abstract class AbstractODataError implements ODataError {
this.target = target;
}
@Override
public List<ODataErrorDetail> getDetails() {
return details;
}
public void setDetails(final List<ODataErrorDetail> detail) {
this.details = detail;
}
@Override
public Dictionary<String,Object> getInnerError() {
return innerError;
}
public void setInnerError(final Dictionary<String,Object> innerError) {
this.innerError = innerError;
}
}

View File

@ -20,13 +20,23 @@ package org.apache.olingo.commons.core.data;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import org.apache.olingo.commons.api.Constants;
import org.apache.olingo.commons.api.data.Entity;
import org.apache.olingo.commons.api.data.ResWrap;
import org.apache.olingo.commons.api.domain.ODataErrorDetail;
public class JSONODataErrorDeserializer extends AbstractJsonDeserializer<JSONODataErrorImpl> {
@ -54,8 +64,30 @@ public class JSONODataErrorDeserializer extends AbstractJsonDeserializer<JSONODa
if (errorNode.has(Constants.ERROR_TARGET)) {
error.setTarget(errorNode.get(Constants.ERROR_TARGET).textValue());
}
if (errorNode.hasNonNull(Constants.ERROR_DETAILS)) {
List<ODataErrorDetail> details = new ArrayList<ODataErrorDetail>();
for (final Iterator<JsonNode> itor = errorNode.get(Constants.ERROR_DETAILS).iterator(); itor.hasNext();) {
details.add(
itor.next().traverse(parser.getCodec()).<ResWrap<JSONODataErrorDetailImpl>>readValueAs(
new TypeReference<JSONODataErrorDetailImpl>() {
}).getPayload());
}
error.setDetails(details);
}
if (errorNode.hasNonNull(Constants.ERROR_INNERERROR)) {
JsonNode innerError = errorNode.get(Constants.ERROR_INNERERROR);
Dictionary<String, Object> innerErr = new Hashtable<String, Object>();
for (final Iterator<String> itor = innerError.fieldNames(); itor.hasNext();) {
String keyTmp = itor.next();
String val = innerError.get(keyTmp).toString();
innerErr.put(keyTmp,val);
}
error.setInnerError(innerErr);
}
}
return new ResWrap<JSONODataErrorImpl>((URI) null, null, error);
}
}

View File

@ -0,0 +1,66 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.olingo.commons.core.data;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.apache.olingo.commons.api.Constants;
import org.apache.olingo.commons.api.data.Entity;
import org.apache.olingo.commons.api.data.ResWrap;
import org.apache.olingo.commons.api.domain.ODataErrorDetail;
public class JSONODataErrorDetailDeserializer extends
AbstractJsonDeserializer<JSONODataErrorDetailImpl> {
@Override
protected ResWrap<JSONODataErrorDetailImpl> doDeserialize(
final JsonParser parser, final DeserializationContext ctxt)
throws IOException, JsonProcessingException {
final JSONODataErrorDetailImpl error = new JSONODataErrorDetailImpl();
final JsonNode errorNode = parser.getCodec().readTree(parser);
if (errorNode.has(Constants.ERROR_CODE)) {
error.setCode(errorNode.get(Constants.ERROR_CODE).textValue());
}
if (errorNode.has(Constants.ERROR_MESSAGE)) {
final JsonNode message = errorNode.get(Constants.ERROR_MESSAGE);
if (message.isValueNode()) {
error.setMessage(message.textValue());
} else if (message.isObject()) {
error.setMessage(message.get(Constants.VALUE).asText());
}
}
if (errorNode.has(Constants.ERROR_TARGET)) {
error.setTarget(errorNode.get(Constants.ERROR_TARGET).textValue());
}
return new ResWrap<JSONODataErrorDetailImpl>((URI) null, null, error);
}
}

View File

@ -0,0 +1,62 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.olingo.commons.core.data;
import org.apache.olingo.commons.api.domain.ODataErrorDetail;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
/*
* JSONODataErrorDetailImpl, using the JSONODataErrorDetailDeserializer similar to JSONODataErrorImpl's.
*/
@JsonDeserialize(using = JSONODataErrorDetailDeserializer.class)
public class JSONODataErrorDetailImpl implements ODataErrorDetail {
private String code;
private String message;
private String target;
@Override
public String getCode() {
return code;
}
public void setCode(final String code) {
this.code = code;
}
@Override
public String getMessage() {
return message;
}
public void setMessage(final String message) {
this.message = message;
}
@Override
public String getTarget() {
return target;
}
public void setTarget(final String target) {
this.target = target;
}
}