Leejoy
Leejoy

Reputation: 1446

Error: Not detached model found

I am getting the following error while working with Wicket 6.19:

    Caused by: org.apache.wicket.core.util.objects.checker.CheckingObjectOutputStream$ObjectCheckException: Not detached model found!
A problem occurred while checking object with type: com.test.web.components.CurrentUserModel
Field hierarchy is:
   [class=com.test.web.base.pages.HomePage, path=0]
    private java.lang.Object org.apache.wicket.MarkupContainer.children [class=[Ljava.lang.Object;]
      java.lang.Object org.apache.wicket.Component.data[1] [class=com.test.web.HeaderPanel, path=0:headerPanel]
        private java.lang.Object org.apache.wicket.MarkupContainer.children [class=[Ljava.lang.Object;]
          private java.lang.String org.apache.wicket.markup.html.image.resource.LocalizedImageResource.variation[2] [class=com.test.web.LoginStatusPanel, path=0:headerPanel:loginStatusPanel]
            java.lang.Object org.apache.wicket.Component.data [class=com.test.web.components.CurrentUserModel] <----- field that is causing the problem

Code:

@RequireHttps
public class HomePage extends WebPage {
    private HeaderPanel headerPanel;
    private LoginStatusPanel loginStatusPanel;
    private MenuPanel menuPanel;

    public HomePage(String id) {
        super();
        initialize();
    }

    public HomePage(String id, PageParameters parameters) {
        super(parameters);
        initialize();
    }

    @Override
    protected void onInitialize() {
        Request request = RequestCycle.get().getRequest();
        if (request instanceof ServletWebRequest)
        {
            ServletWebRequest wr = (ServletWebRequest) request;
            HttpSession session = wr.getContainerRequest().getSession();
            if (session != null) {
            }
        }
        super.onInitialize();
    }

    public String getApplicationId(){
        return applicationId;
    }

    private void initialize() {
        add(headerPanel = new HeaderPanel("headerPanel"));
        headerPanel.add(loginStatusPanel = new LoginStatusPanel("loginStatusPanel"));
        headerPanel.add(menuPanel = new MenuPanel("menuPanel"));
    }
}

public class HeaderPanel extends Panel {
    private Label headerTitleLbl;

    public HeaderPanel(String id) {
        super(id);
        add(headerTitleLbl=new Label("headerTitle", WebApplication.getAppTitle()));
        headerTitleLbl.setOutputMarkupId(true);
    }
}

public class LoginStatusPanel extends Panel {

    public LoginStatusPanel(String id) {
        super(id, new CurrentUserModel()); // Line 1
    }

    protected void onInitialize() {
        IModel<Authentication> authModel = (IModel<Authentication>) getDefaultModel();
        final Authentication auth = authModel.getObject();

        if (auth != null && auth.isAuthenticated() && !(auth instanceof AnonymousAuthenticationToken))
        {
            Fragment fragment = new Fragment("frag", "loggedInFragment", this);
            Label status;
            fragment.add(status = new Label("status", "Logged in as "));
            fragment.add(new Label("userId", new PropertyModel<String>(authModel, "name")));
            add(fragment);
        }
        else
        {
            Fragment fragment = new Fragment("frag", "loggedOutFragment", this);
            fragment.add(new Label("status", "Logged out"));
            fragment.add(new ExternalLink("login", "/LoginPage", "Log In"));
            add(fragment);
        }
        super.onInitialize();
    }
}

public class MenuPanel extends Panel {
    public MenuPanel(String id) {
        super(id);
        add(new MenuPanel("menu", WebApplication.getMenuList()));
    }
}

public class CurrentUserModel extends LoadableDetachableModel<Authentication> {
    protected Authentication load() {
        return SecurityContextHolder.getContext().getAuthentication();
    }
}

Note: If I update the code in comment marked as 'Line 1' with the following:

super(id, new Model(SecurityContextHolder.getContext().getAuthentication()));

The error doesn't show up, that might be because its not a LoadableDetachableModel.

Is it possible to pass an instance of LoadableDetachableModel to Component class constructor?

I am getting the same error for the following code as well:

tree = new NestedTree<TreeItem>("tree", provider, state);

Where state is an instance of the following class:

private class TreeItemExpansionModel extends LoadableDetachableModel<Set<TreeItem>>
{
    private static final long serialVersionUID = 1L;
    private final String treePanelId;

    public TreeItemExpansionModel(String treePanelId) {
        this.treePanelId = treePanelId;
    }

    @Override
    protected Set<TreeItem> load() {
        return TreeItemExpansion.get(treePanelId);
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof TreeItemExpansionModel) {
            return ((TreeItemExpansionModel) obj).treePanelId.equals(treePanelId);
        }
        return false;
    }

    @Override
    public int hashCode() {
        return treePanelId.hashCode();
    }
}

And the error is as follows:

Caused by: org.apache.wicket.core.util.objects.checker.CheckingObjectOutputStream$ObjectCheckException: Not detached model found!
A problem occurred while checking object with type: com.test.web.components.TreeItemProvider$TreeItemModel
Field hierarchy is:
   [class=com.test.web.admin.pages.HomePage, path=0]
    private java.lang.Object org.apache.wicket.MarkupContainer.children [class=[Ljava.lang.Object;]
      java.util.List com.test.web.components.WhistlePanel.selectionList[2] [class=com.test.web.NavPanel, path=0:navPanel]
        private java.lang.Object org.apache.wicket.MarkupContainer.children [class=org.apache.wicket.extensions.markup.html.repeater.tree.NestedTree, path=0:navPanel:tree]
          private java.lang.Object org.apache.wicket.MarkupContainer.children [class=org.apache.wicket.extensions.markup.html.repeater.tree.nested.Subtree, path=0:navPanel:tree:subtree]
            private java.lang.Object org.apache.wicket.MarkupContainer.children [class=org.apache.wicket.markup.repeater.RefreshingView, path=0:navPanel:tree:subtree:branches]
              private java.lang.Object org.apache.wicket.MarkupContainer.children [class=[Ljava.lang.Object;]
                private java.lang.Object org.apache.wicket.MarkupContainer.children[0] [class=org.apache.wicket.extensions.markup.html.repeater.tree.nested.BranchItem, path=0:navPanel:tree:subtree:branches:1]
                  java.lang.Object org.apache.wicket.Component.data [class=com.test.web.components.TreeItemProvider$TreeItemModel] <----- field that is causing the problem

Thanks in advance.

Upvotes: 0

Views: 431

Answers (2)

Leejoy
Leejoy

Reputation: 1446

I know that the information I have provided myself here is incomplete for anyone to give a proper solution.

I have posted the same query in another forum and got the solution. So I thought of share that link here so that it may be useful for people who visit this page.

Thanks

Upvotes: -1

Martin Strejc
Martin Strejc

Reputation: 4347

The problem is the combination of the Serialization checker and your managing of the lifecycle of models.

When you put a LoadableDetachableModel and you initialize the model that is NOT Serializable, Wicket checker throws an exception that you try to serialize data that is not Serializable. That behavior is correct.

The reason why it happen is

  1. Authentication is not Serializable
  2. You call getDefaultModel().get() that loads the model object even though it's a lazy loading model

See the NotDetachedModelChecker https://github.com/apache/wicket/blob/build/wicket-6.20.0/wicket-core/src/main/java/org/apache/wicket/core/util/objects/checker/NotDetachedModelChecker.java

Suggested Fix:

I. Use AbstractReadOnlyModel instead of LoadableDetachableModel

    public class CurrentUserModel extends AbstractReadOnlyModel<Authentication> {
       protected Authentication getObject() {
          return SecurityContextHolder.getContext().getAuthentication();
       }
    }

If you really need to keep the value (I think it is not required, because the Spring authentication context returs ever the same value at each attempt), wrapper the LoadableDetachbleModel with AbstractReadOnlyModel.

II. Detach the model: call authModel.detach(); before you return flow control back to Wicket

protected void onInitialize() {
    IModel<Authentication> authModel = (IModel<Authentication>) getDefaultModel();
    final Authentication auth = authModel.getObject();

    if (auth != null && auth.isAuthenticated() && !(auth instanceof AnonymousAuthenticationToken))
    {
        Fragment fragment = new Fragment("frag", "loggedInFragment", this);
        Label status;
        fragment.add(status = new Label("status", "Logged in as "));
        fragment.add(new Label("userId", new PropertyModel<String>(authModel, "name")));
        add(fragment);
    }
    else
    {
        Fragment fragment = new Fragment("frag", "loggedOutFragment", this);
        fragment.add(new Label("status", "Logged out"));
        fragment.add(new ExternalLink("login", "/LoginPage", "Log In"));
        add(fragment);
    }
    authModel.detach();
    super.onInitialize();
}

Upvotes: 2

Related Questions