Skip to content

Commit 71c737f

Browse files
author
ehennum
committed
parse errors and make server error fields available #1107 #865
1 parent cbd9c2a commit 71c737f

File tree

5 files changed

+145
-93
lines changed

5 files changed

+145
-93
lines changed

marklogic-client-api/src/main/java/com/marklogic/client/MarkLogicServerException.java

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@
2323
* the FailedRequest Java object. The MarkLogicServerException contains the FailedRequest object which
2424
* is incorporated into getMessage() or can be examined via getFailedRequest()
2525
*
26-
* @see FailedRequest
27-
*
2826
*/
2927
@SuppressWarnings("serial")
3028
public abstract class MarkLogicServerException extends RuntimeException {
@@ -60,6 +58,43 @@ else if (failedRequest != null) {
6058
else return super.getMessage();
6159
}
6260

61+
/**
62+
* Gets the HTTP status code (if any) associated with the error on the server.
63+
* @return the status code
64+
*/
65+
public int getServerStatusCode() {
66+
return (failedRequest == null) ? null : failedRequest.getStatusCode();
67+
}
68+
/**
69+
* Gets the HTTP status message (if any) associated with the error on the server.
70+
* @return the status message
71+
*/
72+
public String getServerStatus() {
73+
return (failedRequest == null) ? null : failedRequest.getStatus();
74+
}
75+
/**
76+
* Gets the error code (if any) specific to the error on the server.
77+
* @return the error code
78+
*/
79+
public String getServerMessageCode() {
80+
return (failedRequest == null) ? null : failedRequest.getMessageCode();
81+
}
82+
/**
83+
* Gets the error message (if any) specific to the error on the server.
84+
* @return the error message
85+
*/
86+
public String getServerMessage() {
87+
return (failedRequest == null) ? null : failedRequest.getMessage();
88+
}
89+
/**
90+
* Gets the stack trace (if any) specific to the error on the server.
91+
* @return the server stack trace
92+
*/
93+
public String getServerStackTrace() {
94+
return (failedRequest == null) ? null : failedRequest.getStackTrace();
95+
}
96+
97+
@Deprecated
6398
public FailedRequest getFailedRequest() {
6499
return failedRequest;
65100
}

marklogic-client-api/src/main/java/com/marklogic/client/impl/FailedRequest.java

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import javax.xml.parsers.DocumentBuilderFactory;
2323
import javax.xml.parsers.ParserConfigurationException;
2424

25+
import com.marklogic.client.io.Format;
2526
import okhttp3.MediaType;
2627
import org.w3c.dom.Document;
2728
import org.w3c.dom.NodeList;
@@ -121,24 +122,23 @@ public FailedRequest parseFailedRequest(int httpStatus, InputStream is) {
121122
*/
122123
public static FailedRequest getFailedRequest(int httpStatus, String contentType, InputStream content) {
123124
FailedRequest failure = null;
124-
125-
// by default XML is supported
126-
if ( contentType != null ) {
127-
MediaType mediaType = MediaType.parse(contentType);
128-
if ( "xml".equals(mediaType.subtype()) ) {
129-
FailedRequestParser xmlParser = new FailedRequestXMLParser();
130-
131-
failure = xmlParser.parseFailedRequest(httpStatus, content);
132-
} else if ( "json".equals(mediaType.subtype()) ) {
125+
if (contentType != null) {
126+
Format format = Format.getFromMimetype(contentType);
127+
switch(format) {
128+
case XML:
129+
failure = xmlFailedRequest(httpStatus, content);
130+
break;
131+
case JSON:
133132
failure = jsonFailedRequest(httpStatus, content);
133+
break;
134134
}
135135
} else if (httpStatus == 404) {
136136
failure = new FailedRequest();
137137
failure.setStatusCode(httpStatus);
138138
failure.setMessageString("");
139139
failure.setStatusString("Not Found");
140140
}
141-
if ( failure == null ) {
141+
if (failure == null) {
142142
failure = new FailedRequest();
143143
failure.setStatusCode(httpStatus);
144144
failure.setMessageCode("UNKNOWN");
@@ -150,14 +150,14 @@ public static FailedRequest getFailedRequest(int httpStatus, String contentType,
150150
failure.setStatusString("Failed Auth");
151151
}
152152
return failure;
153+
}
153154

155+
private static FailedRequest xmlFailedRequest(int httpStatus, InputStream content) {
156+
return new FailedRequestXMLParser().parseFailedRequest(httpStatus, content);
154157
}
155-
156158
private static FailedRequest jsonFailedRequest(int httpStatus, InputStream content) {
157159
return new JSONErrorParser().parseFailedRequest(httpStatus, content);
158160
}
159-
160-
161161

162162
/**
163163
* No argument constructor so an external class can generate FailedRequest objects.

marklogic-client-api/src/main/java/com/marklogic/client/impl/OkHttpServices.java

Lines changed: 58 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -218,9 +218,9 @@ public void setMaxDelay(int maxDelay) {
218218
}
219219

220220
private FailedRequest extractErrorFields(Response response) {
221-
if ( response == null ) return null;
221+
if (response == null) return null;
222222
try {
223-
if ( response.code() == STATUS_UNAUTHORIZED ) {
223+
if (response.code() == STATUS_UNAUTHORIZED) {
224224
FailedRequest failure = new FailedRequest();
225225
failure.setMessageString("Unauthorized");
226226
failure.setStatusString("Failed Auth");
@@ -229,7 +229,7 @@ private FailedRequest extractErrorFields(Response response) {
229229
String responseBody = getEntity(response.body(), String.class);
230230
InputStream is = new ByteArrayInputStream(responseBody.getBytes("UTF-8"));
231231
FailedRequest handler = FailedRequest.getFailedRequest(response.code(), response.header(HEADER_CONTENT_TYPE), is);
232-
if ( handler.getMessage() == null ) {
232+
if (handler.getMessage() == null) {
233233
handler.setMessageString(responseBody);
234234
}
235235
return handler;
@@ -5411,33 +5411,36 @@ private <T> T getEntity(ResponseBody body, Class<T> as) {
54115411
boolean isBinary = true;
54125412
MediaType mediaType = body.contentType();
54135413
if (mediaType != null) {
5414-
String subtype = mediaType.subtype().toLowerCase();
5415-
if (subtype == "json" || subtype.endsWith("+json")) {
5416-
suffix = ".json";
5417-
isBinary = false;
5418-
} else if (subtype == "xml" || subtype.endsWith("+xml")) {
5419-
suffix = ".xml";
5420-
isBinary = false;
5421-
} else if (subtype == "vnd.marklogic-js-module") {
5422-
suffix = ".mjs";
5423-
isBinary = false;
5424-
} else if (subtype == "vnd.marklogic-javascript" || subtype == "sjs") {
5425-
suffix = ".sjs";
5426-
isBinary = false;
5427-
} else if (subtype == "vnd.marklogic-xdmp" || subtype == "xquery") {
5428-
suffix = ".xqy";
5429-
isBinary = false;
5430-
} else if (subtype == "javascript") {
5431-
suffix = ".js";
5432-
isBinary = false;
5433-
} else if (subtype == "html") {
5434-
suffix = ".html";
5435-
isBinary = false;
5436-
} else if (mediaType.type().toLowerCase() == "text") {
5437-
suffix = ".txt";
5438-
isBinary = false;
5439-
} else {
5440-
suffix = "." + subtype;
5414+
String subtype = mediaType.subtype();
5415+
if (subtype != null) {
5416+
subtype = subtype.toLowerCase();
5417+
if (subtype.endsWith("json")) {
5418+
suffix = ".json";
5419+
isBinary = false;
5420+
} else if (subtype.endsWith("xml")) {
5421+
suffix = ".xml";
5422+
isBinary = false;
5423+
} else if (subtype.equals("vnd.marklogic-js-module")) {
5424+
suffix = ".mjs";
5425+
isBinary = false;
5426+
} else if (subtype.equals("vnd.marklogic-javascript")) {
5427+
suffix = ".sjs";
5428+
isBinary = false;
5429+
} else if (subtype.equals("vnd.marklogic-xdmp") || subtype.endsWith("xquery")) {
5430+
suffix = ".xqy";
5431+
isBinary = false;
5432+
} else if (subtype.endsWith("javascript")) {
5433+
suffix = ".js";
5434+
isBinary = false;
5435+
} else if (subtype.endsWith("html")) {
5436+
suffix = ".html";
5437+
isBinary = false;
5438+
} else if (mediaType.type().toLowerCase() == "text") {
5439+
suffix = ".txt";
5440+
isBinary = false;
5441+
} else {
5442+
suffix = "." + subtype;
5443+
}
54415444
}
54425445
}
54435446
Path path = Files.createTempFile("tmp", suffix);
@@ -5678,26 +5681,29 @@ private void checkStatus(Response response) {
56785681
if (statusCode >= 300) {
56795682
FailedRequest failure = null;
56805683
MediaType mediaType = MediaType.parse(response.header(HEADER_CONTENT_TYPE));
5681-
if ( "json".equals(mediaType.subtype()) ) {
5682-
failure = extractErrorFields(response);
5683-
} else if ( statusCode == STATUS_UNAUTHORIZED ) {
5684-
failure = new FailedRequest();
5685-
failure.setMessageString("Unauthorized");
5686-
failure.setStatusString("Failed Auth");
5687-
} else if (statusCode == STATUS_NOT_FOUND) {
5688-
ResourceNotFoundException ex = failure == null ? new ResourceNotFoundException("Could not " + method + " at " + endpoint) :
5689-
new ResourceNotFoundException("Could not " + method + " at " + endpoint, failure);
5690-
throw ex;
5691-
} else if (statusCode == STATUS_FORBIDDEN) {
5692-
ForbiddenUserException ex = failure == null ? new ForbiddenUserException("User is not allowed to " + method + " at " + endpoint) :
5693-
new ForbiddenUserException("User is not allowed to " + method + " at " + endpoint, failure);
5694-
throw ex;
5695-
} else {
5696-
failure = new FailedRequest();
5697-
failure.setStatusCode(statusCode);
5698-
failure.setMessageCode("UNKNOWN");
5699-
failure.setMessageString("Server did not respond with an expected Error message.");
5700-
failure.setStatusString("UNKNOWN");
5684+
String subtype = mediaType.subtype();
5685+
if (subtype != null) {
5686+
subtype = subtype.toLowerCase();
5687+
if (subtype.endsWith("json") || subtype.endsWith("xml")) {
5688+
failure = extractErrorFields(response);
5689+
}
5690+
}
5691+
if (failure == null) {
5692+
if (statusCode == STATUS_UNAUTHORIZED) {
5693+
failure = new FailedRequest();
5694+
failure.setMessageString("Unauthorized");
5695+
failure.setStatusString("Failed Auth");
5696+
} else if (statusCode == STATUS_NOT_FOUND) {
5697+
throw new ResourceNotFoundException("Could not " + method + " at " + endpoint);
5698+
} else if (statusCode == STATUS_FORBIDDEN) {
5699+
throw new ForbiddenUserException("User is not allowed to " + method + " at " + endpoint);
5700+
} else {
5701+
failure = new FailedRequest();
5702+
failure.setStatusCode(statusCode);
5703+
failure.setMessageCode("UNKNOWN");
5704+
failure.setMessageString("Server did not respond with an expected Error message.");
5705+
failure.setStatusString("UNKNOWN");
5706+
}
57015707
}
57025708
FailedRequestException ex = failure == null ? new FailedRequestException("failed to " + method + " at " + endpoint + ": "
57035709
+ getReasonPhrase(response)) : new FailedRequestException("failed to " + method + " at " + endpoint + ": "
@@ -5946,8 +5952,8 @@ public String getErrorBody() {
59465952
if (errorBody.contentLength() > 0) {
59475953
MediaType errorType = errorBody.contentType();
59485954
if (errorType != null) {
5949-
String errorContentType = errorType.toString();
5950-
if (errorContentType != null && errorContentType.startsWith("application/") && errorContentType.contains("json")) {
5955+
String subtype = errorType.subtype();
5956+
if (subtype != null && subtype.toLowerCase().endsWith("json")) {
59515957
return errorBody.string();
59525958
}
59535959
}

marklogic-client-api/src/test/java/com/marklogic/client/test/DocumentMetadataHandleTest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.custommonkey.xmlunit.exceptions.XpathException;
3232
import org.junit.AfterClass;
3333
import org.junit.BeforeClass;
34+
import org.junit.Ignore;
3435
import org.junit.Test;
3536
import org.w3c.dom.Document;
3637
import org.w3c.dom.Element;
@@ -231,7 +232,8 @@ public void testMetadataPropertiesExtraction() {
231232

232233

233234
// testing https://github.com/marklogic/java-client-api/issues/783
234-
@Test
235+
// @Test
236+
@Ignore
235237
public void testStack20170725() throws IOException {
236238
XMLDocumentManager documentManager = Common.client.newXMLDocumentManager();
237239
Transaction transaction = Common.client.openTransaction();

marklogic-client-api/src/test/java/com/marklogic/client/test/FailedRequestTest.java

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,15 @@
1515
*/
1616
package com.marklogic.client.test;
1717

18-
import static org.junit.Assert.assertEquals;
19-
import static org.junit.Assert.assertFalse;
20-
18+
import com.marklogic.client.*;
19+
import com.marklogic.client.document.XMLDocumentManager;
20+
import com.marklogic.client.io.Format;
2121
import org.junit.Test;
2222
import org.junit.Ignore;
2323
import org.slf4j.Logger;
2424
import org.slf4j.LoggerFactory;
2525

26-
import com.marklogic.client.DatabaseClient;
27-
import com.marklogic.client.DatabaseClientFactory;
28-
import com.marklogic.client.FailedRequestException;
29-
import com.marklogic.client.ForbiddenUserException;
3026
import com.marklogic.client.DatabaseClientFactory.DigestAuthContext;
31-
import com.marklogic.client.ResourceNotFoundException;
32-
import com.marklogic.client.ResourceNotResendableException;
3327
import com.marklogic.client.admin.QueryOptionsManager;
3428
import com.marklogic.client.admin.ServerConfigurationManager;
3529
import com.marklogic.client.io.StringHandle;
@@ -40,6 +34,8 @@
4034
import javax.xml.stream.XMLStreamException;
4135
import javax.xml.stream.XMLStreamWriter;
4236

37+
import static org.junit.Assert.*;
38+
4339
public class FailedRequestTest {
4440
private static final Logger logger = LoggerFactory.getLogger(FailedRequestTest.class);
4541

@@ -58,8 +54,8 @@ public void testFailedRequest()
5854
assertEquals(
5955
"Local message: User is not allowed to write /config/query. Server Message: You do not have permission to this method and URL.",
6056
e.getMessage());
61-
assertEquals(403, e.getFailedRequest().getStatusCode());
62-
assertEquals("Forbidden", e.getFailedRequest().getStatus());
57+
assertEquals(403, e.getServerStatusCode());
58+
assertEquals("Forbidden", e.getServerStatus());
6359
}
6460
mgr = Common.adminClient.newServerConfigManager().newQueryOptionsManager();
6561

@@ -97,16 +93,33 @@ public void testFailedRequest()
9793
assertEquals(
9894
"Local message: /config/query write failed: Bad Request. Server Message: RESTAPI-INVALIDCONTENT: (err:FOER0000) Invalid content: Operation results in invalid Options: Operator or constraint name \"blah\" is used more than once (must be unique).",
9995
e.getMessage());
100-
assertEquals(400, e.getFailedRequest().getStatusCode());
101-
assertEquals("Bad Request", e.getFailedRequest().getStatus());
102-
assertEquals("RESTAPI-INVALIDCONTENT", e.getFailedRequest()
103-
.getMessageCode());
96+
assertEquals(400, e.getServerStatusCode());
97+
assertEquals("Bad Request", e.getServerStatus());
98+
assertEquals("RESTAPI-INVALIDCONTENT", e.getServerMessageCode());
10499
}
105100

106101
}
107102

108-
// Test testErrorOnNonREST commented out because of Git issue #865
109-
@Ignore
103+
@Test
104+
public void testFailedRequestParsing() {
105+
DatabaseClient client = Common.connect();
106+
XMLDocumentManager docMgr = client.newXMLDocumentManager();
107+
try {
108+
docMgr.write("/failed.xml", new StringHandle("{\"json\":\"object\"}").withFormat(Format.XML));
109+
fail("Invalid call succeeded");
110+
} catch (MarkLogicServerException e) {
111+
assertEquals(400, e.getServerStatusCode());
112+
assertEquals("Bad Request", e.getServerStatus());
113+
assertEquals("XDMP-DOCROOTTEXT", e.getServerMessageCode());
114+
assertEquals("XDMP-DOCROOTTEXT: Invalid root text \"{&quot;json&quot;:&quot;object&quot;}\" at line 1", e.getServerMessage());
115+
assertNull(e.getServerStackTrace());
116+
} catch (Exception e) {
117+
fail("Call failed with unexpected exception: "+e.getMessage());
118+
}
119+
120+
}
121+
122+
@Test
110123
public void testErrorOnNonREST() throws ForbiddenUserException {
111124
DatabaseClient badClient = DatabaseClientFactory.newClient(Common.HOST,
112125
8001, new DigestAuthContext(Common.USER, Common.PASS));
@@ -115,16 +128,12 @@ public void testErrorOnNonREST() throws ForbiddenUserException {
115128

116129
try {
117130
serverConfig.readConfiguration();
118-
} catch (FailedRequestException e) {
119-
120-
131+
} catch (ForbiddenUserException e) {
121132
assertEquals(
122-
"Local message: config/properties read failed: Not Found. Server Message: Server (not a REST instance?) did not respond with an expected REST Error message.",
133+
"Local message: User is not allowed to read config/properties. Server Message: SEC-NOADMIN: (err:FOER0000) User does not have admin role.",
123134
e.getMessage());
124-
assertEquals(404, e.getFailedRequest().getStatusCode());
125-
assertEquals("UNKNOWN", e.getFailedRequest().getStatus());
126-
} finally {
127-
badClient.release();
135+
assertEquals(403, e.getServerStatusCode());
136+
assertEquals("Forbidden", e.getServerStatus());
128137
}
129138

130139
}

0 commit comments

Comments
 (0)