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.

Reply via email to