5
0
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:
Bilung Lee 2012-10-17 09:38:15 -07:00
parent a46b25f5e5
commit 5807db8c3e
13 changed files with 293 additions and 83 deletions

View File

@ -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"),
;

View File

@ -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());
}
}

View File

@ -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);
} }
}
}
}

View File

@ -137,4 +137,4 @@ protected void resolveVariables(List arg) {
}
Collections.copy(arg, temp);
}
}
}

View File

@ -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();

View File

@ -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
}
}

View File

@ -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;
}
}

View 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());
}
}
}

View File

@ -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());
}
}

View File

@ -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();
}

View File

@ -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>();

View File

@ -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);
}

View File

@ -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;