merge with branch issue-107

This commit is contained in:
Sebastian Sdorra
2012-03-14 07:51:32 +01:00
20 changed files with 463 additions and 56 deletions

View File

@@ -0,0 +1,209 @@
/**
* Copyright (c) 2010, Sebastian Sdorra All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer. 2. Redistributions in
* binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other
* materials provided with the distribution. 3. Neither the name of SCM-Manager;
* nor the names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.api.rest;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Objects;
import com.google.common.base.Throwables;
//~--- JDK imports ------------------------------------------------------------
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
/**
*
* @author Sebastian Sdorra
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "exception")
public class RestExceptionResult
{
/**
* Constructs ...
*
*/
public RestExceptionResult() {}
/**
* Constructs ...
*
*
* @param throwable
*/
public RestExceptionResult(Throwable throwable)
{
this(throwable.getMessage(), throwable);
}
/**
* Constructs ...
*
*
* @param message
* @param stacktrace
*/
public RestExceptionResult(String message, String stacktrace)
{
this.message = message;
this.stacktrace = stacktrace;
}
/**
* Constructs ...
*
*
* @param message
* @param throwable
*/
public RestExceptionResult(String message, Throwable throwable)
{
this(message, Throwables.getStackTraceAsString(throwable));
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param obj
*
* @return
*/
@Override
public boolean equals(Object obj)
{
if (obj == null)
{
return false;
}
if (getClass() != obj.getClass())
{
return false;
}
final RestExceptionResult other = (RestExceptionResult) obj;
return Objects.equal(message, other.message)
&& Objects.equal(stacktrace, other.stacktrace);
}
/**
* Method description
*
*
* @return
*/
@Override
public int hashCode()
{
return Objects.hashCode(message, stacktrace);
}
/**
* Method description
*
*
* @return
*/
@Override
public String toString()
{
//J-
return Objects.toStringHelper(this)
.add("message", message)
.add("stacktrace", stacktrace)
.toString();
//J+
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @return
*/
public String getMessage()
{
return message;
}
/**
* Method description
*
*
* @return
*/
public String getStacktrace()
{
return stacktrace;
}
//~--- set methods ----------------------------------------------------------
/**
* Method description
*
*
* @param message
*/
public void setMessage(String message)
{
this.message = message;
}
/**
* Method description
*
*
* @param stacktrace
*/
public void setStacktrace(String stacktrace)
{
this.stacktrace = stacktrace;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private String message;
/** Field description */
private String stacktrace;
}

View File

@@ -43,6 +43,7 @@ import org.slf4j.LoggerFactory;
import sonia.scm.LastModifiedAware; import sonia.scm.LastModifiedAware;
import sonia.scm.Manager; import sonia.scm.Manager;
import sonia.scm.ModelObject; import sonia.scm.ModelObject;
import sonia.scm.api.rest.RestExceptionResult;
import sonia.scm.security.ScmSecurityException; import sonia.scm.security.ScmSecurityException;
import sonia.scm.util.AssertUtil; import sonia.scm.util.AssertUtil;
import sonia.scm.util.HttpUtil; import sonia.scm.util.HttpUtil;
@@ -59,6 +60,7 @@ import javax.ws.rs.core.EntityTag;
import javax.ws.rs.core.GenericEntity; import javax.ws.rs.core.GenericEntity;
import javax.ws.rs.core.Request; import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriInfo; import javax.ws.rs.core.UriInfo;
/** /**
@@ -154,12 +156,12 @@ public abstract class AbstractManagerResource<T extends ModelObject,
catch (ScmSecurityException ex) catch (ScmSecurityException ex)
{ {
logger.warn("create is not allowd", ex); logger.warn("create is not allowd", ex);
response = Response.status(Response.Status.FORBIDDEN).build(); response = Response.status(Status.FORBIDDEN).build();
} }
catch (Exception ex) catch (Exception ex)
{ {
logger.error("error during create", ex); logger.error("error during create", ex);
response = Response.serverError().build(); response = createErrorResonse(ex);
} }
return response; return response;
@@ -195,7 +197,7 @@ public abstract class AbstractManagerResource<T extends ModelObject,
catch (Exception ex) catch (Exception ex)
{ {
logger.error("error during create", ex); logger.error("error during create", ex);
response = Response.serverError().build(); response = createErrorResonse(ex);
} }
} }
@@ -234,7 +236,7 @@ public abstract class AbstractManagerResource<T extends ModelObject,
catch (Exception ex) catch (Exception ex)
{ {
logger.error("error during create", ex); logger.error("error during create", ex);
response = Response.serverError().build(); response = createErrorResonse(ex);
} }
return response; return response;
@@ -363,6 +365,51 @@ public abstract class AbstractManagerResource<T extends ModelObject,
//~--- methods -------------------------------------------------------------- //~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param throwable
*
* @return
*/
protected Response createErrorResonse(Throwable throwable)
{
return createErrorResonse(Status.INTERNAL_SERVER_ERROR,
throwable.getMessage(), throwable);
}
/**
* Method description
*
*
* @param status
* @param throwable
*
* @return
*/
protected Response createErrorResonse(Status status, Throwable throwable)
{
return createErrorResonse(status, throwable.getMessage(), throwable);
}
/**
* Method description
*
*
* @param status
* @param message
* @param throwable
*
* @return
*/
protected Response createErrorResonse(Status status, String message,
Throwable throwable)
{
return Response.status(status).entity(new RestExceptionResult(message,
throwable)).build();
}
/** /**
* Method description * Method description
* *

View File

@@ -633,8 +633,7 @@ public class RepositoryResource
catch (Exception ex) catch (Exception ex)
{ {
logger.error("could not retrive content", ex); logger.error("could not retrive content", ex);
response = response = createErrorResonse(ex);
Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
} }
} }

View File

@@ -142,6 +142,7 @@
<!-- sonia.action --> <!-- sonia.action -->
<script type="text/javascript" src="resources/js/action/sonia.action.js"></script> <script type="text/javascript" src="resources/js/action/sonia.action.js"></script>
<script type="text/javascript" src="resources/js/action/sonia.action.changepasswordwindow.js"></script> <script type="text/javascript" src="resources/js/action/sonia.action.changepasswordwindow.js"></script>
<script type="text/javascript" src="resources/js/action/sonia.action.exceptionwindow.js"></script>
<!-- sonia.plugin --> <!-- sonia.plugin -->
<script type="text/javascript" src="resources/js/plugin/sonia.plugin.js"></script> <script type="text/javascript" src="resources/js/plugin/sonia.plugin.js"></script>

View File

@@ -0,0 +1,96 @@
/* *
* Copyright (c) 2010, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
Sonia.action.ExceptionWindow = Ext.extend(Ext.Window,{
title: null,
message: null,
stacktrace: null,
icon: Ext.MessageBox.ERROR,
// TODO i18n
// labels
okText: 'Ok',
detailsText: 'Details',
exceptionText: 'Exception',
initComponent: function(){
var config = {
title: this.title,
width: 300,
height: 110,
closable: true,
resizable: true,
plain: true,
border: false,
modal: true,
bodyCssClass: 'x-window-dlg',
items: [{
xtype: 'panel',
height: 45,
bodyCssClass: 'x-panel-mc x-dlg-icon',
html: '<div class="ext-mb-icon ' + this.icon + '"></div><div class="ext-mb-content"><span class="ext-mb-text">' + this.message + '</span></div>'
},{
id: 'stacktraceArea',
xtype: 'textarea',
editable: false,
fieldLabel: this.exceptionText,
value: this.stacktrace,
width: '98%',
height: 260,
hidden: true
}],
buttons: [{
text: this.okText,
scope: this,
handler: this.close
},{
id: 'detailButton',
text: this.detailsText,
scope: this,
handler: this.showDetails
}]
}
Ext.apply(this, Ext.apply(this.initialConfig, config));
Sonia.action.ChangePasswordWindow.superclass.initComponent.apply(this, arguments);
},
showDetails: function(){
Ext.getCmp('detailButton').setDisabled(true);
Ext.getCmp('stacktraceArea').setVisible(true);
this.setWidth(640);
this.setHeight(380);
this.center();
}
});

View File

@@ -205,8 +205,8 @@ Sonia.config.ScmConfigPanel = Ext.extend(Sonia.config.ConfigPanel,{
}, },
failure: function(result){ failure: function(result){
this.el.unmask(); this.el.unmask();
main.handleFailure( main.handleRestFailure(
result.status, result,
this.errorTitleText, this.errorTitleText,
this.errorMsgText this.errorMsgText
); );
@@ -234,8 +234,8 @@ Sonia.config.ScmConfigPanel = Ext.extend(Sonia.config.ConfigPanel,{
failure: function(result){ failure: function(result){
el.unmask(); el.unmask();
clearTimeout(tid); clearTimeout(tid);
main.handleFailure( main.handleRestFailure(
result.status, result,
this.errorTitleText, this.errorTitleText,
this.errorMsgText this.errorMsgText
); );

View File

@@ -54,8 +54,8 @@ Sonia.config.SimpleConfigForm = Ext.extend(Sonia.config.ConfigForm,{
}, },
failure: function(result){ failure: function(result){
this.el.unmask(); this.el.unmask();
main.handleFailure( main.handleRestFailure(
result.status, result,
null, null,
this.failedText this.failedText
); );
@@ -79,8 +79,8 @@ Sonia.config.SimpleConfigForm = Ext.extend(Sonia.config.ConfigForm,{
failure: function(result){ failure: function(result){
el.unmask(); el.unmask();
clearTimeout(tid); clearTimeout(tid);
main.handleFailure( main.handleRestFailure(
result.status, result,
null, null,
this.failedText this.failedText
); );

View File

@@ -81,8 +81,8 @@ Sonia.group.FormPanel = Ext.extend(Sonia.rest.FormPanel,{
this.fireEvent('updateFailed', group); this.fireEvent('updateFailed', group);
clearTimeout(tid); clearTimeout(tid);
el.unmask(); el.unmask();
main.handleFailure( main.handleRestFailure(
result.status, result,
this.errorTitleText, this.errorTitleText,
this.updateErrorMsgText this.updateErrorMsgText
); );
@@ -124,8 +124,8 @@ Sonia.group.FormPanel = Ext.extend(Sonia.rest.FormPanel,{
this.fireEvent('creationFailed', item); this.fireEvent('creationFailed', item);
clearTimeout(tid); clearTimeout(tid);
el.unmask(); el.unmask();
main.handleFailure( main.handleRestFailure(
result.status, result,
this.errorTitleText, this.errorTitleText,
this.createErrorMsgText this.createErrorMsgText
); );

View File

@@ -119,8 +119,8 @@ Sonia.group.Panel = Ext.extend(Sonia.rest.Panel, {
this.resetPanel(); this.resetPanel();
}, },
failure: function(result){ failure: function(result){
main.handleFailure( main.handleRestFailure(
result.status, result,
this.errorTitleText, this.errorTitleText,
this.errorMsgText this.errorMsgText
); );

View File

@@ -214,8 +214,8 @@ Sonia.panel.SyntaxHighlighterPanel = Ext.extend(Ext.Panel, {
this.highlight(); this.highlight();
}, },
failure: function(result){ failure: function(result){
main.handleFailure( main.handleRestFailure(
result.status, result,
this.loadErrorTitleText, this.loadErrorTitleText,
this.loadErrorMsgText this.loadErrorMsgText
); );

View File

@@ -99,8 +99,8 @@ Sonia.plugin.Center = Ext.extend(Ext.util.Observable, {
console.debug('plugin installation failed'); console.debug('plugin installation failed');
} }
loadingBox.hide(); loadingBox.hide();
main.handleFailure( main.handleRestFailure(
result.status, result,
this.errorTitleText, this.errorTitleText,
this.installFailedText this.installFailedText
); );
@@ -133,8 +133,8 @@ Sonia.plugin.Center = Ext.extend(Ext.util.Observable, {
console.debug('plugin uninstallation failed'); console.debug('plugin uninstallation failed');
} }
loadingBox.hide(); loadingBox.hide();
main.handleFailure( main.handleRestFailure(
result.status, result,
this.errorTitleText, this.errorTitleText,
this.uninstallFailedText this.uninstallFailedText
); );
@@ -168,8 +168,8 @@ Sonia.plugin.Center = Ext.extend(Ext.util.Observable, {
console.debug('plugin update failed'); console.debug('plugin update failed');
} }
loadingBox.hide(); loadingBox.hide();
main.handleFailure( main.handleRestFailure(
result.status, result,
this.errorTitleText, this.errorTitleText,
this.updateFailedText this.updateFailedText
); );

View File

@@ -120,8 +120,8 @@ Sonia.repository.CommitPanel = Ext.extend(Ext.Panel, {
this.update(changeset) this.update(changeset)
}, },
failure: function(result){ failure: function(result){
main.handleFailure( main.handleRestFailure(
result.status, result,
this.errorTitleText, this.errorTitleText,
this.errorMsgText this.errorMsgText
); );

View File

@@ -148,8 +148,8 @@ Sonia.repository.FormPanel = Ext.extend(Sonia.rest.FormPanel,{
this.fireEvent('creationFailed', item); this.fireEvent('creationFailed', item);
clearTimeout(tid); clearTimeout(tid);
el.unmask(); el.unmask();
main.handleFailure( main.handleRestFailure(
result.status, result,
this.errorTitleText, this.errorTitleText,
this.createErrorMsgText this.createErrorMsgText
); );

View File

@@ -101,8 +101,8 @@ Sonia.repository.ImportWindow = Ext.extend(Ext.Window,{
this.doLayout(); this.doLayout();
}, },
failure: function(result){ failure: function(result){
main.handleFailure( main.handleRestFailure(
result.status, result,
this.errorTitleText, this.errorTitleText,
this.errorMsgText this.errorMsgText
); );
@@ -218,8 +218,8 @@ Sonia.repository.ImportWindow = Ext.extend(Ext.Window,{
this.appendImported(obj); this.appendImported(obj);
}, },
failure: function(result){ failure: function(result){
main.handleFailure( main.handleRestFailure(
result.status, result,
this.errorTitleText, this.errorTitleText,
this.errorMsgText this.errorMsgText
); );

View File

@@ -138,8 +138,8 @@ Sonia.repository.get = function(id, callback){
execCallback(Ext.decode(response.responseText)); execCallback(Ext.decode(response.responseText));
}, },
failure: function(result){ failure: function(result){
main.handleFailure( main.handleRestFailure(
result.status result
); );
} }
}); });

View File

@@ -206,8 +206,8 @@ Sonia.repository.Panel = Ext.extend(Sonia.rest.Panel, {
this.resetPanel(); this.resetPanel();
}, },
failure: function(result){ failure: function(result){
main.handleFailure( main.handleRestFailure(
result.status, result,
this.errorTitleText, this.errorTitleText,
this.errorMsgText this.errorMsgText
); );

View File

@@ -51,8 +51,8 @@ Sonia.rest.JsonStore = Ext.extend( Ext.data.JsonStore, {
if (debug){ if (debug){
console.debug( 'error during store load, status: ' + status ); console.debug( 'error during store load, status: ' + status );
} }
main.handleFailure( main.handleRestFailure(
status, response,
this.errorTitleText, this.errorTitleText,
this.errorMsgText this.errorMsgText
); );

View File

@@ -65,6 +65,14 @@ Sonia.scm.Main = Ext.extend(Ext.util.Observable, {
errorSessionExpiredTitle: 'Session expired', errorSessionExpiredTitle: 'Session expired',
errorSessionExpiredMessage: 'Your session is expired. Please relogin.', errorSessionExpiredMessage: 'Your session is expired. Please relogin.',
// TODO i18n
errorNoPermissionsTitle: 'Not permitted',
errorNoPermissionsMessage: 'You have not enough permissions to execute this action.',
errorNotFoundTitle: 'Not found',
errorNotFoundMessage: 'The resource could not be found.',
mainTabPanel: null, mainTabPanel: null,
@@ -382,12 +390,17 @@ Sonia.scm.Main = Ext.extend(Ext.util.Observable, {
console.debug( "callback is not a function or object. " + callback ); console.debug( "callback is not a function or object. " + callback );
} }
}, this); }, this);
}, },
handleFailure: function(status, title, message){ handleRestFailure: function(response, title, message){
this.handleFailure(response.status, title, message, response.responseText);
},
handleFailure: function(status, title, message, serverException){
if (debug){ if (debug){
console.debug( 'handle failure for status code: ' + status ); console.debug( 'handle failure for status code: ' + status );
} }
// TODO handle already exists exceptions specific
if ( status == 401 ){ if ( status == 401 ){
Ext.Msg.show({ Ext.Msg.show({
title: this.errorSessionExpiredTitle, title: this.errorSessionExpiredTitle,
@@ -400,6 +413,18 @@ Sonia.scm.Main = Ext.extend(Ext.util.Observable, {
}, },
scope: this scope: this
}); });
} else if ( status == 403 ){
Ext.Msg.show({
title: this.errorNoPermissionsTitle,
msg: this.errorNoPermissionsMessage,
buttons: Ext.Msg.OKCANCEL
});
} else if ( status == 404 ){
Ext.Msg.show({
title: this.errorNotFoundTitle,
msg: this.errorNotFoundMessage,
buttons: Ext.Msg.OKCANCEL
});
} else { } else {
if ( title == null ){ if ( title == null ){
title = this.errorTitle; title = this.errorTitle;
@@ -407,12 +432,42 @@ Sonia.scm.Main = Ext.extend(Ext.util.Observable, {
if ( message == null ){ if ( message == null ){
message = this.errorMessage; message = this.errorMessage;
} }
Ext.MessageBox.show({
title: title, var text = null;
msg: String.format(message, status), if (serverException){
buttons: Ext.MessageBox.OK, try {
icon:Ext.MessageBox.ERROR if ( Ext.isString(serverException) ){
}); serverException = Ext.decode(serverException);
}
text = serverException.stacktrace;
if ( debug ){
console.debug( text );
}
} catch (e){
if ( debug ){
console.debug(e);
}
}
}
message = String.format(message, status);
if ( text == null ){
Ext.MessageBox.show({
title: title,
msg: message,
buttons: Ext.MessageBox.OK,
icon: Ext.MessageBox.ERROR
});
} else {
new Sonia.action.ExceptionWindow({
title: title,
message: message,
stacktrace: text
}).show();
}
} }
}, },

View File

@@ -134,8 +134,8 @@ Sonia.user.FormPanel = Ext.extend(Sonia.rest.FormPanel,{
this.execCallback(this.onUpdate, item); this.execCallback(this.onUpdate, item);
}, },
failure: function(result){ failure: function(result){
main.handleFailure( main.handleRestFailure(
result.status, result,
this.errorTitleText, this.errorTitleText,
this.updateErrorMsgText this.updateErrorMsgText
); );
@@ -165,8 +165,8 @@ Sonia.user.FormPanel = Ext.extend(Sonia.rest.FormPanel,{
this.execCallback(this.onCreate, user); this.execCallback(this.onCreate, user);
}, },
failure: function(result){ failure: function(result){
main.handleFailure( main.handleRestFailure(
result.status, result,
this.errorTitleText, this.errorTitleText,
this.createErrorMsgText this.createErrorMsgText
); );

View File

@@ -140,8 +140,8 @@ Sonia.user.Panel = Ext.extend(Sonia.rest.Panel, {
this.resetPanel(); this.resetPanel();
}, },
failure: function(result){ failure: function(result){
main.handleFailure( main.handleRestFailure(
result.status, result,
this.errorTitleText, this.errorTitleText,
this.errorMsgText this.errorMsgText
); );