mirror of
https://github.com/apache/sqoop.git
synced 2025-05-16 00:41:23 +08:00
SQOOP-629: Provide better exception handling during server-client communication
(Jarek Jarcec Cecho)
This commit is contained in:
parent
a46b25f5e5
commit
5807db8c3e
@ -39,7 +39,7 @@ public enum ClientError implements ErrorCode {
|
||||
/** We're not able to get user input */
|
||||
CLIENT_0005("Can't get user input"),
|
||||
|
||||
/** There occured exception on server side **/
|
||||
/** There occurred exception on server side **/
|
||||
CLIENT_0006("Server has returned exception"),
|
||||
|
||||
;
|
||||
|
@ -28,7 +28,7 @@
|
||||
import org.apache.sqoop.client.core.ClientError;
|
||||
import org.apache.sqoop.common.SqoopException;
|
||||
import org.apache.sqoop.common.SqoopProtocolConstants;
|
||||
import org.apache.sqoop.json.ExceptionInfo;
|
||||
import org.apache.sqoop.json.ThrowableBean;
|
||||
import org.json.simple.JSONObject;
|
||||
import org.json.simple.JSONValue;
|
||||
|
||||
@ -91,13 +91,13 @@ public ClientResponse handle(ClientRequest cr) {
|
||||
if(resp.getHeaders().containsKey(
|
||||
SqoopProtocolConstants.HEADER_SQOOP_INTERNAL_ERROR_CODE)) {
|
||||
|
||||
ExceptionInfo ex = new ExceptionInfo();
|
||||
ThrowableBean ex = new ThrowableBean();
|
||||
|
||||
String responseText = resp.getEntity(String.class);
|
||||
JSONObject json = (JSONObject) JSONValue.parse(responseText);
|
||||
ex.restore(json);
|
||||
|
||||
throw new SqoopException(ClientError.CLIENT_0006, ex.toString());
|
||||
throw new SqoopException(ClientError.CLIENT_0006, ex.getThrowable());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -37,6 +37,7 @@ public DeleteCommand(Shell shell) {
|
||||
"Delete", "info");
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Object execute(List args) {
|
||||
if (args.size() == 0) {
|
||||
@ -59,5 +60,6 @@ public Object execute(List args) {
|
||||
} else {
|
||||
String msg = "Usage: delete " + getUsage();
|
||||
throw new SqoopException(ClientError.CLIENT_0002, msg);
|
||||
} }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -137,4 +137,4 @@ protected void resolveVariables(List arg) {
|
||||
}
|
||||
Collections.copy(arg, temp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,8 @@
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.apache.sqoop.client.utils.ThrowableDisplayer;
|
||||
import org.codehaus.groovy.runtime.MethodClosure;
|
||||
import org.codehaus.groovy.tools.shell.Command;
|
||||
import org.codehaus.groovy.tools.shell.CommandRegistry;
|
||||
import org.codehaus.groovy.tools.shell.Groovysh;
|
||||
@ -45,6 +47,11 @@ public static void main (String[] args) throws Exception
|
||||
System.setProperty("groovysh.prompt", "sqoop");
|
||||
Groovysh shell = new Groovysh();
|
||||
|
||||
// Install our error hook (exception handling)
|
||||
shell.setErrorHook(
|
||||
new MethodClosure(ThrowableDisplayer.class, "errorHook"));
|
||||
ThrowableDisplayer.setIo(shell.getIo());
|
||||
|
||||
CommandRegistry registry = shell.getRegistry();
|
||||
@SuppressWarnings("unchecked")
|
||||
Iterator<Command> iterator = registry.iterator();
|
||||
|
@ -0,0 +1,97 @@
|
||||
/**
|
||||
* 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.sqoop.client.utils;
|
||||
|
||||
import org.apache.sqoop.client.core.ClientError;
|
||||
import org.apache.sqoop.common.SqoopException;
|
||||
import org.codehaus.groovy.tools.shell.IO;
|
||||
|
||||
/**
|
||||
* Pretty printing of Throwable objects
|
||||
*/
|
||||
public class ThrowableDisplayer {
|
||||
|
||||
/**
|
||||
* Associated shell IO object.
|
||||
*
|
||||
* This objects needs to be set explicitly as some of the methods are called
|
||||
* by Groovy shell without ability to pass additional arguments.
|
||||
*/
|
||||
private static IO io;
|
||||
|
||||
public static void setIo(IO ioObject) {
|
||||
io = ioObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Error hook installed to Groovy shell.
|
||||
*
|
||||
* Will display exception that appeared during executing command. In most
|
||||
* cases we will simply delegate the call to printing throwable method,
|
||||
* however in case that we've received ClientError.CLIENT_0006 (server
|
||||
* exception), we will unwrap server issue and view only that as local
|
||||
* context shouldn't make any difference.
|
||||
*
|
||||
* @param t Throwable to be displayed
|
||||
*/
|
||||
public static void errorHook(Throwable t) {
|
||||
io.out.println("@|red Exception has occurred during processing command |@");
|
||||
|
||||
// If this is server exception from server
|
||||
if(t instanceof SqoopException
|
||||
&& ((SqoopException)t).getErrorCode() == ClientError.CLIENT_0006) {
|
||||
io.out.print("@|red Server has returned exception: |@");
|
||||
printThrowable(io, t.getCause());
|
||||
} else {
|
||||
printThrowable(io, t);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pretty print Throwable instance including stack trace and causes.
|
||||
*
|
||||
* @param io IO object to use for generating output
|
||||
* @param t Throwable to display
|
||||
*/
|
||||
protected static void printThrowable(IO io, Throwable t) {
|
||||
io.out.print("@|red Exception: |@");
|
||||
io.out.print(t.getClass().getName());
|
||||
io.out.print(" @|red Message: |@");
|
||||
io.out.print(t.getMessage());
|
||||
io.out.println();
|
||||
|
||||
io.out.println("Stack trace:");
|
||||
for(StackTraceElement e : t.getStackTrace()) {
|
||||
io.out.print("\t @|bold at |@ ");
|
||||
io.out.print(e.getClassName());
|
||||
io.out.print(" (@|bold " + e.getFileName() + ":"
|
||||
+ e.getLineNumber() + ") |@ ");
|
||||
io.out.println();
|
||||
}
|
||||
|
||||
Throwable cause = t.getCause();
|
||||
if(cause != null) {
|
||||
io.out.print("Caused by: ");
|
||||
printThrowable(io, cause);
|
||||
}
|
||||
}
|
||||
|
||||
private ThrowableDisplayer() {
|
||||
// Instantiation is prohibited
|
||||
}
|
||||
}
|
@ -1,73 +0,0 @@
|
||||
/**
|
||||
* 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.sqoop.json;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
public class ExceptionInfo implements JsonBean {
|
||||
|
||||
public static final String ERROR_CODE = "error-code";
|
||||
public static final String ERROR_MESSAGE = "error-message";
|
||||
public static final String STACK_TRACE = "stack-trace";
|
||||
|
||||
private String errorCode;
|
||||
private String errorMessage;
|
||||
private String stackTrace;
|
||||
|
||||
// For "extract"
|
||||
public ExceptionInfo(String code, String message, Exception ex) {
|
||||
errorCode = code;
|
||||
errorMessage = message;
|
||||
|
||||
StringWriter writer = new StringWriter();
|
||||
ex.printStackTrace(new PrintWriter(writer));
|
||||
writer.flush();
|
||||
|
||||
stackTrace = writer.getBuffer().toString();
|
||||
}
|
||||
|
||||
// For "restore"
|
||||
public ExceptionInfo() {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public JSONObject extract() {
|
||||
JSONObject result = new JSONObject();
|
||||
result.put(ERROR_CODE, errorCode);
|
||||
result.put(ERROR_MESSAGE, errorMessage);
|
||||
result.put(STACK_TRACE, stackTrace);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restore(JSONObject jsonObject) {
|
||||
errorCode = (String) jsonObject.get(ERROR_CODE);
|
||||
errorMessage = (String) jsonObject.get(ERROR_MESSAGE);
|
||||
stackTrace = (String) jsonObject.get(STACK_TRACE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return errorMessage;
|
||||
}
|
||||
}
|
116
common/src/main/java/org/apache/sqoop/json/ThrowableBean.java
Normal file
116
common/src/main/java/org/apache/sqoop/json/ThrowableBean.java
Normal file
@ -0,0 +1,116 @@
|
||||
/**
|
||||
* 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.sqoop.json;
|
||||
|
||||
import org.json.simple.JSONArray;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Transfer throwable.
|
||||
*
|
||||
* TODO(jarcec): After SQOOP-627 will get committed, change the throwable
|
||||
* creation to same class as was on the server instead of Throwable.
|
||||
*/
|
||||
public class ThrowableBean implements JsonBean {
|
||||
|
||||
public static final String MESSAGE = "message";
|
||||
public static final String STACK_TRACE = "stack-trace";
|
||||
public static final String CLASS = "class";
|
||||
public static final String METHOD = "method";
|
||||
public static final String FILE = "file";
|
||||
public static final String LINE = "line";
|
||||
public static final String CAUSE = "cause";
|
||||
|
||||
private Throwable throwable;
|
||||
|
||||
// For "extract"
|
||||
public ThrowableBean(Throwable ex) {
|
||||
throwable = ex;
|
||||
}
|
||||
|
||||
// For "restore"
|
||||
public ThrowableBean() {
|
||||
}
|
||||
|
||||
public Throwable getThrowable() {
|
||||
return throwable;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public JSONObject extract() {
|
||||
JSONObject result = new JSONObject();
|
||||
|
||||
result.put(MESSAGE, throwable.getMessage());
|
||||
result.put(CLASS, throwable.getClass().getName());
|
||||
|
||||
JSONArray st = new JSONArray();
|
||||
for(StackTraceElement element : throwable.getStackTrace()) {
|
||||
JSONObject obj = new JSONObject();
|
||||
|
||||
obj.put(CLASS, element.getClassName());
|
||||
obj.put(METHOD, element.getMethodName());
|
||||
obj.put(FILE, element.getFileName());
|
||||
obj.put(LINE, element.getLineNumber());
|
||||
|
||||
st.add(obj);
|
||||
}
|
||||
|
||||
result.put(STACK_TRACE, st);
|
||||
|
||||
Throwable cause = throwable.getCause();
|
||||
if(cause != null) {
|
||||
ThrowableBean causeBean = new ThrowableBean(cause);
|
||||
result.put(CAUSE, causeBean.extract());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restore(JSONObject jsonObject) {
|
||||
throwable = new Throwable((String) jsonObject.get(MESSAGE));
|
||||
|
||||
List<StackTraceElement> st = new LinkedList<StackTraceElement>();
|
||||
for(Object object : (JSONArray)jsonObject.get(STACK_TRACE)) {
|
||||
JSONObject json = (JSONObject)object;
|
||||
StackTraceElement element = new StackTraceElement(
|
||||
(String)json.get(CLASS),
|
||||
(String)json.get(METHOD),
|
||||
(String)json.get(FILE),
|
||||
((Long)json.get(LINE)).intValue()
|
||||
);
|
||||
st.add(element);
|
||||
}
|
||||
|
||||
throwable.setStackTrace(st.toArray(new StackTraceElement[]{}));
|
||||
|
||||
Object cause = jsonObject.get(CAUSE);
|
||||
if(cause != null) {
|
||||
JSONObject causeJson = (JSONObject)cause;
|
||||
|
||||
ThrowableBean causeBean = new ThrowableBean();
|
||||
causeBean.restore(causeJson);
|
||||
|
||||
throwable.initCause(causeBean.getThrowable());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
/**
|
||||
* 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.sqoop.json;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
import org.json.simple.JSONObject;
|
||||
import org.json.simple.JSONValue;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class TestThrowableBean extends TestCase {
|
||||
public void testSerialization() {
|
||||
Throwable ex = new RuntimeException("A");
|
||||
ex.initCause(new Exception("B"));
|
||||
|
||||
// Serialize it to JSON object
|
||||
ThrowableBean bean = new ThrowableBean(ex);
|
||||
JSONObject json = bean.extract();
|
||||
|
||||
// "Move" it across network in text form
|
||||
String string = json.toJSONString();
|
||||
|
||||
// Retrieved transferred object
|
||||
JSONObject retrievedJson = (JSONObject) JSONValue.parse(string);
|
||||
ThrowableBean retrievedBean = new ThrowableBean();
|
||||
retrievedBean.restore(retrievedJson);
|
||||
Throwable retrieved = retrievedBean.getThrowable();
|
||||
|
||||
assertEquals("A", retrieved.getMessage());
|
||||
assertEquals("B", retrieved.getCause().getMessage());
|
||||
assertNull(retrieved.getCause().getCause());
|
||||
}
|
||||
}
|
@ -77,6 +77,10 @@ public static ResourceBundle getResourceBundle(long connectorId,
|
||||
|
||||
public static MConnector getConnectorMetadata(long connectorId) {
|
||||
ConnectorHandler handler = handlerMap.get(nameMap.get(connectorId));
|
||||
if(handler == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return handler.getMetadata();
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,7 @@
|
||||
import org.apache.sqoop.model.MConnector;
|
||||
import org.apache.sqoop.server.RequestContext;
|
||||
import org.apache.sqoop.server.RequestHandler;
|
||||
import org.apache.sqoop.server.common.ServerError;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
@ -58,6 +59,11 @@ public JsonBean handleEvent(RequestContext ctx) {
|
||||
} else {
|
||||
Long id = Long.parseLong(cid);
|
||||
|
||||
// Check that user is not asking for non existing connector id
|
||||
if(!ConnectorManager.getConnectoIds().contains(id)) {
|
||||
throw new SqoopException(ServerError.SERVER_0004, "Invalid id " + id);
|
||||
}
|
||||
|
||||
connectors = new LinkedList<MConnector>();
|
||||
bundles = new LinkedList<ResourceBundle>();
|
||||
|
||||
|
@ -26,7 +26,7 @@
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
import org.apache.sqoop.common.ErrorCode;
|
||||
import org.apache.sqoop.json.ExceptionInfo;
|
||||
import org.apache.sqoop.json.ThrowableBean;
|
||||
import org.apache.sqoop.common.SqoopException;
|
||||
import org.apache.sqoop.common.SqoopProtocolConstants;
|
||||
import org.apache.sqoop.common.SqoopResponseCode;
|
||||
@ -136,11 +136,10 @@ private void sendErrorResponse(RequestContext ctx, Exception ex)
|
||||
SqoopProtocolConstants.HEADER_SQOOP_INTERNAL_ERROR_MESSAGE,
|
||||
ex.getMessage());
|
||||
|
||||
ExceptionInfo exceptionInfo = new ExceptionInfo(ec.getCode(),
|
||||
ex.getMessage(), ex);
|
||||
ThrowableBean throwableBean = new ThrowableBean(ex);
|
||||
|
||||
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
||||
response.getWriter().write(exceptionInfo.extract().toJSONString());
|
||||
response.getWriter().write(throwableBean.extract().toJSONString());
|
||||
} else {
|
||||
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
@ -33,6 +33,9 @@ public enum ServerError implements ErrorCode {
|
||||
/** We've received invalid HTTP request */
|
||||
SERVER_0003("Invalid HTTP request"),
|
||||
|
||||
/** Invalid argument in HTTP request */
|
||||
SERVER_0004("Invalid argument in HTTP request"),
|
||||
|
||||
;
|
||||
|
||||
private final String message;
|
||||
|
Loading…
Reference in New Issue
Block a user