Hey.
I met the same problem when I started to implement my app using Wicket and
GAE. On GAE there is problem with serialization. GAE prohibits to use
replacement substitution, so WIcket's methods like Component#modelChanged()
or Component#modelChanging() cause AccessControlContext. To fix this problem
I extended TabbedPanel class and overwrote some significant methods. Here is
code for you. It works on GAE for my application more then 3 month without
any problem.
/**
* @author dominity
*
* Implementation of Wicket's {...@link TabbedPanel} that has ability to work
on
* Google App Engine. Currently there is problem with serialization of
model.
* GAE prohibits to use replacement substitution, so any time tab switching
is
* invoked, AccessControlContext is threw. To avoid this behavior
* {...@link Component#setDefaultModelObject(Object)} is overwritten to
* hide {...@link Component#modelChanging()} and
* {...@link Component#modelChanged()} methods invocation before/after model's
* object setting. Also, {...@link TabbedPanel#setSelectedTab(int)} is
overwritten
* to switch from using of {...@link Component#setDefaultModelObject(Object)}
to
* {...@link #setDefModelObject(Object)}.
*/
public class GAETabbedPanel extends TabbedPanel {
// copy of boolean array from TabbedPanel to avoid serialization
problems on
// GAE
private transient Boolean[] tabsVisibilityCache;
/**
* @param id
* id of span on markup page
* @param tabs
* list of {...@link ITab}s
* @see TabbedPanel
*/
public GAETabbedPanel( String id, List<ITab> tabs ) {
super( id, tabs );
}
@Override
public void setSelectedTab( int index ) {
if ( index < 0 || ( index >= getTabs().size() && index > 0 ) ) {
throw new IndexOutOfBoundsException();
}
// here is only change in comparison to
TabbedPanel#setSelectedTab(int)
setDefModelObject( new Integer( index ) );
final Component component;
if ( getTabs().size() == 0 || !isTabVisible( index ) ) {
// no tabs or the currently selected tab is not visible
component = new WebMarkupContainer( TAB_PANEL_ID );
} else {
// show panel from selected tab
ITab tab = getTabs().get( index );
component = tab.getPanel( TAB_PANEL_ID );
if ( component == null ) {
throw new WicketRuntimeException(
"ITab.getPanel() returned null. TabbedPanel ["
+ getPath() + "] ITab index [" + index + "]"
);
}
}
if ( !component.getId().equals( TAB_PANEL_ID ) ) {
throw new WicketRuntimeException(
"ITab.getPanel() returned a panel with invalid id ["
+ component.getId()
+ "]. You must always return a panel with id
equal "
+ "to the provided panelId parameter.
TabbedPanel ["
+ getPath() + "] ITab index [" + index + "]" );
}
addOrReplace( component );
}
/* duplication of the same method at TabbedPanel class in case of
* setDefaultModelObject(Object) overwriting.*/
private boolean isTabVisible( int tabIndex ) {
if ( tabsVisibilityCache == null ) {
tabsVisibilityCache = new Boolean[ getTabs().size() ];
}
if ( tabsVisibilityCache.length > 0 ) {
Boolean visible = tabsVisibilityCache[ tabIndex ];
if ( visible == null ) {
visible = getTabs().get( tabIndex ).isVisible();
tabsVisibilityCache[ tabIndex ] = visible;
}
return visible;
} else {
return false;
}
}
/**
* It's duplication of {...@link #setDefaultModelObject(Object)} except it
* doesn't invoke {...@link #modelChanging()} and {...@link #modelChanged()}
* before/after model's object setting. This prevents
* {...@link AccessControlException} is threw during tab switching.
*
* @param object
* The object to set
* @return this
* @see #setDefaultModelObject(Object)
*/
@SuppressWarnings( "unchecked" )
public final Component setDefModelObject( final Object object ) {
final IModel<Object> model = (IModel<Object>) getDefaultModel();
// Check whether anything can be set at all
if ( model == null ) {
throw new IllegalStateException(
"Attempt to set model object on null model of component:
"
+ getPageRelativePath() );
}
// Check authorization
if ( !isActionAuthorized( ENABLE ) ) {
throw new UnauthorizedActionException( this, ENABLE );
}
// Check whether this will result in an actual change
if ( !getModelComparator().compare( this, object ) ) {
// modelChanging();
model.setObject( object );
// modelChanged();
}
return this;
}
}
Best regards, Alexander.