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.Manager;
import sonia.scm.ModelObject;
import sonia.scm.api.rest.RestExceptionResult;
import sonia.scm.security.ScmSecurityException;
import sonia.scm.util.AssertUtil;
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.Request;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriInfo;
/**
@@ -154,12 +156,12 @@ public abstract class AbstractManagerResource<T extends ModelObject,
catch (ScmSecurityException ex)
{
logger.warn("create is not allowd", ex);
response = Response.status(Response.Status.FORBIDDEN).build();
response = Response.status(Status.FORBIDDEN).build();
}
catch (Exception ex)
{
logger.error("error during create", ex);
response = Response.serverError().build();
response = createErrorResonse(ex);
}
return response;
@@ -195,7 +197,7 @@ public abstract class AbstractManagerResource<T extends ModelObject,
catch (Exception 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)
{
logger.error("error during create", ex);
response = Response.serverError().build();
response = createErrorResonse(ex);
}
return response;
@@ -363,6 +365,51 @@ public abstract class AbstractManagerResource<T extends ModelObject,
//~--- 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
*

View File

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

View File

@@ -142,6 +142,7 @@
<!-- sonia.action -->
<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.exceptionwindow.js"></script>
<!-- sonia.plugin -->
<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){
this.el.unmask();
main.handleFailure(
result.status,
main.handleRestFailure(
result,
this.errorTitleText,
this.errorMsgText
);
@@ -234,8 +234,8 @@ Sonia.config.ScmConfigPanel = Ext.extend(Sonia.config.ConfigPanel,{
failure: function(result){
el.unmask();
clearTimeout(tid);
main.handleFailure(
result.status,
main.handleRestFailure(
result,
this.errorTitleText,
this.errorMsgText
);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -65,6 +65,14 @@ Sonia.scm.Main = Ext.extend(Ext.util.Observable, {
errorSessionExpiredTitle: 'Session expired',
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,
@@ -382,12 +390,17 @@ Sonia.scm.Main = Ext.extend(Ext.util.Observable, {
console.debug( "callback is not a function or object. " + callback );
}
}, 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){
console.debug( 'handle failure for status code: ' + status );
}
// TODO handle already exists exceptions specific
if ( status == 401 ){
Ext.Msg.show({
title: this.errorSessionExpiredTitle,
@@ -400,6 +413,18 @@ Sonia.scm.Main = Ext.extend(Ext.util.Observable, {
},
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 {
if ( title == null ){
title = this.errorTitle;
@@ -407,12 +432,42 @@ Sonia.scm.Main = Ext.extend(Ext.util.Observable, {
if ( message == null ){
message = this.errorMessage;
}
Ext.MessageBox.show({
title: title,
msg: String.format(message, status),
buttons: Ext.MessageBox.OK,
icon:Ext.MessageBox.ERROR
});
var text = null;
if (serverException){
try {
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);
},
failure: function(result){
main.handleFailure(
result.status,
main.handleRestFailure(
result,
this.errorTitleText,
this.updateErrorMsgText
);
@@ -165,8 +165,8 @@ Sonia.user.FormPanel = Ext.extend(Sonia.rest.FormPanel,{
this.execCallback(this.onCreate, user);
},
failure: function(result){
main.handleFailure(
result.status,
main.handleRestFailure(
result,
this.errorTitleText,
this.createErrorMsgText
);

View File

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