• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

knowledgepixels / nanodash / 29227150127

13 Jul 2026 05:46AM UTC coverage: 28.504% (+0.02%) from 28.481%
29227150127

push

github

web-flow
Merge pull request #552 from knowledgepixels/fix/apicache-replaced-eviction-npe

fix: prevent ApiCache metadata wipe on entry replacement causing home page hang

1872 of 7381 branches covered (25.36%)

Branch coverage included in aggregate %.

3777 of 12437 relevant lines covered (30.37%)

4.52 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
src/main/java/com/knowledgepixels/nanodash/page/HomePage.java
1
package com.knowledgepixels.nanodash.page;
2

3
import com.knowledgepixels.nanodash.NanodashPreferences;
4
import com.knowledgepixels.nanodash.NanodashSession;
5
import com.knowledgepixels.nanodash.Utils;
6
import com.knowledgepixels.nanodash.WicketApplication;
7
import com.knowledgepixels.nanodash.component.ResultComponent;
8
import com.knowledgepixels.nanodash.component.TitleBar;
9
import com.knowledgepixels.nanodash.component.ViewList;
10
import org.apache.wicket.markup.html.panel.Fragment;
11
import com.knowledgepixels.nanodash.domain.MaintainedResource;
12
import com.knowledgepixels.nanodash.repository.MaintainedResourceRepository;
13
import org.apache.wicket.Component;
14
import org.apache.wicket.extensions.ajax.markup.html.AjaxLazyLoadPanel;
15
import org.apache.wicket.markup.html.basic.Label;
16
import org.apache.wicket.model.IModel;
17
import org.apache.wicket.model.LoadableDetachableModel;
18
import org.apache.wicket.request.mapper.parameter.PageParameters;
19
import org.apache.wicket.util.string.Strings;
20

21
/**
22
 * The home page of Nanodash, showing the views defined for the configured home resource.
23
 */
24
public class HomePage extends NanodashPage {
25

26
    /**
27
     * The mount path for this page.
28
     */
29
    public static final String MOUNT_PATH = "/";
30

31
    /**
32
     * {@inheritDoc}
33
     */
34
    @Override
35
    public String getMountPath() {
36
        return MOUNT_PATH;
×
37
    }
38

39
    /**
40
     * Constructor for the home page.
41
     *
42
     * @param parameters the page parameters
43
     */
44
    public HomePage(final PageParameters parameters) {
45
        super(parameters);
×
46

47
        add(new TitleBar("titlebar", this, null));
×
48
        final NanodashSession session = NanodashSession.get();
×
49
        String v = WicketApplication.getThisVersion();
×
50
        String lv = WicketApplication.getLatestVersion();
×
51
        if (NanodashPreferences.get().isOrcidLoginMode()) {
×
52
            add(new Label("warning", ""));
×
53
        } else if (v.endsWith("-SNAPSHOT")) {
×
54
            add(new Label("warning", "You are running a temporary snapshot version of Nanodash (" + v + "). The latest public version is " + lv + "."));
×
55
        } else if (lv != null && !v.equals(lv)) {
×
56
            add(new Label("warning", "There is a new version available: " + lv + ". You are currently using " + v + ". " +
×
57
                                     "Run 'update' (Unix/Mac) or 'update-under-windows.bat' (Windows) to update to the latest version, or manually download it " +
58
                                     "<a href=\"" + WicketApplication.LATEST_RELEASE_URL + "\">here</a>.").setEscapeModelStrings(false));
×
59
        } else {
60
            add(new Label("warning", ""));
×
61
        }
62
        if (NanodashPreferences.get().isReadOnlyMode()) {
×
63
            add(new Label("text", "This is a read-only instance, so you cannot publish new nanopublications here."));
×
64
        } else if (NanodashSession.get().isProfileComplete()) {
×
65
            add(new Label("text", ""));
×
66
        } else if (NanodashPreferences.get().isOrcidLoginMode() && session.getUserIri() == null) {
×
67
            String loginUrl = OrcidLoginPage.getOrcidLoginUrl(".");
×
68
            add(new Label("text", "In order to see your own nanopublications and publish new ones, <a href=\"" + loginUrl + "\">login to ORCID</a> first.").setEscapeModelStrings(false));
×
69
        } else {
×
70
            // A logged-in user completes their profile on their own About page; only fall
71
            // back to the profile page when there is no resolved user IRI yet.
72
            String profileUrl = session.getUserIri() != null
×
73
                    ? UserPage.MOUNT_PATH + "?id=" + Utils.urlEncode(session.getUserIri()) + "&tab=about"
×
74
                    : ProfilePage.MOUNT_PATH;
×
75
            add(new Label("text", "Before you can start, you first need to <a href=\"" + profileUrl + "\">complete your profile</a>.").setEscapeModelStrings(false));
×
76
        }
77

78
        setOutputMarkupId(true);
×
79

80
        final String homeResourceId = NanodashPreferences.get().getHomeResource();
×
81
        final MaintainedResource homeResource = MaintainedResourceRepository.get().findById(homeResourceId);
×
82

83
        // Rendered only for a genuine misconfiguration (see below): the resource
84
        // repository is warm but the configured id is not a known maintained resource.
85
        final String notFoundHtml = "<div class=\"row-section\"><div class=\"col-12\"><p class=\"negative\">" +
×
86
                "Configured home resource <code>" + Strings.escapeMarkup(homeResourceId) + "</code> could not be found. " +
×
87
                "Set the <code>NANODASH_HOME_RESOURCE</code> environment variable to a valid maintained-resource IRI." +
88
                "</p></div></div>";
89

90
        if (homeResource == null && MaintainedResourceRepository.get().isReady()) {
×
91
            // The repository has a full snapshot, yet the configured id isn't in it:
92
            // a real misconfiguration, not a cold-cache race.
93
            add(new Label("views", notFoundHtml).setEscapeModelStrings(false));
×
94
            return;
×
95
        }
96

97
        if (homeResource != null) {
×
98
            homeResource.triggerDataUpdate();
×
99
            if (homeResource.isDataInitialized()) {
×
100
                ViewList viewList = new ViewList("views", homeResource);
×
101
                viewList.setPageFooter(new Fragment("page-footer", "homeFooterFragment", this));
×
102
                add(viewList);
×
103
                return;
×
104
            }
105
        }
106

107
        // Either the resource exists but its data isn't initialized yet, or the
108
        // repository itself is still cold so findById is transiently null (cache
109
        // refresh in flight / racing spaces load). Lazy-load and poll until the data
110
        // resolves, rather than declaring a hard "not found" on a transient null. If
111
        // the repository warms up without the configured id, the misconfig notice is
112
        // shown then.
113
        // Resolve the repository singleton inside the anonymous classes rather than
114
        // capturing it: MaintainedResourceRepository is not Serializable, and a
115
        // captured reference makes the whole page fail to serialize to the page store.
116
        final IModel<MaintainedResource> homeResourceModel = new LoadableDetachableModel<MaintainedResource>() {
×
117
            @Override
118
            protected MaintainedResource load() {
119
                return MaintainedResourceRepository.get().findById(homeResourceId);
×
120
            }
121
        };
122

123
        add(new AjaxLazyLoadPanel<Component>("views") {
×
124

125
            @Override
126
            public Component getLazyLoadComponent(String markupId) {
127
                MaintainedResource r = homeResourceModel.getObject();
×
128
                if (r == null) {
×
129
                    return new Label(markupId, notFoundHtml).setEscapeModelStrings(false);
×
130
                }
131
                ViewList viewList = new ViewList(markupId, r);
×
132
                viewList.setPageFooter(new Fragment("page-footer", "homeFooterFragment", HomePage.this));
×
133
                return viewList;
×
134
            }
135

136
            @Override
137
            protected boolean isContentReady() {
138
                MaintainedResource r = homeResourceModel.getObject();
×
139
                // isDataInitialized() also kicks off the (idempotent) data load.
140
                if (r != null) return r.isDataInitialized();
×
141
                // Resource still unresolved: keep polling while the repository is cold,
142
                // and stop (to show the misconfig notice) only once it is warm.
143
                return MaintainedResourceRepository.get().isReady();
×
144
            }
145

146
            @Override
147
            public Component getLoadingComponent(String id) {
148
                return new Label(id, "<div class=\"row-section\"><div class=\"col-12\">" + ResultComponent.getWaitIconHtml() + "</div></div>").setEscapeModelStrings(false);
×
149
            }
150

151
            @Override
152
            protected void onDetach() {
153
                homeResourceModel.detach();
×
154
                super.onDetach();
×
155
            }
×
156

157
        });
158
    }
×
159

160
    /**
161
     * <p>hasAutoRefreshEnabled.</p>
162
     *
163
     * @return a boolean
164
     */
165
    protected boolean hasAutoRefreshEnabled() {
166
        return true;
×
167
    }
168

169
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc