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

knowledgepixels / nanodash / 23441640250

23 Mar 2026 02:10PM UTC coverage: 16.351% (-0.01%) from 16.361%
23441640250

push

github

tkuhn
feat: refresh cache and redirect to new space/resource after publish

When creating spaces, subspaces, or maintained resources, the cache is now
properly refreshed and the user is redirected to the newly created entity's
page instead of back to the parent or list page.

- SpaceExploreMenu: use space ID as refresh target (refreshes parent space
  data + repositories) and set redirect to the new entity's page
- WicketApplication: also refresh MaintainedResourceRepository when the
  refresh target is a Space
- ExplorePage: use the introduced IRI from the published nanopub as the
  redirect ID, so the redirect goes to the new space/resource page
- SpaceListPage: redirect to the new space's page instead of /spaces
- PublishForm: skip context-based redirect when postpub-redirect-url is
  set, so the redirect URL takes priority

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

746 of 5627 branches covered (13.26%)

Branch coverage included in aggregate %.

1894 of 10519 relevant lines covered (18.01%)

2.47 hits per line

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

67.47
src/main/java/com/knowledgepixels/nanodash/WicketApplication.java
1
package com.knowledgepixels.nanodash;
2

3
import com.google.gson.Gson;
4
import com.google.gson.reflect.TypeToken;
5
import com.knowledgepixels.nanodash.connector.*;
6
import com.knowledgepixels.nanodash.connector.ios.DsNanopubPage;
7
import com.knowledgepixels.nanodash.connector.ios.DsOverviewPage;
8
import com.knowledgepixels.nanodash.connector.pensoft.BdjNanopubPage;
9
import com.knowledgepixels.nanodash.connector.pensoft.BdjOverviewPage;
10
import com.knowledgepixels.nanodash.connector.pensoft.RioNanopubPage;
11
import com.knowledgepixels.nanodash.connector.pensoft.RioOverviewPage;
12
import com.knowledgepixels.nanodash.domain.AbstractResourceWithProfile;
13
import com.knowledgepixels.nanodash.domain.MaintainedResource;
14
import com.knowledgepixels.nanodash.domain.Space;
15
import com.knowledgepixels.nanodash.events.NanopubPublishedListener;
16
import com.knowledgepixels.nanodash.events.NanopubPublishedPublisher;
17
import com.knowledgepixels.nanodash.page.*;
18
import com.knowledgepixels.nanodash.repository.MaintainedResourceRepository;
19
import com.knowledgepixels.nanodash.repository.SpaceRepository;
20
import de.agilecoders.wicket.webjars.WicketWebjars;
21
import org.apache.http.HttpResponse;
22
import org.apache.http.client.methods.HttpGet;
23
import org.apache.http.impl.client.CloseableHttpClient;
24
import org.apache.http.impl.client.HttpClientBuilder;
25
import org.apache.wicket.RuntimeConfigurationType;
26
import org.apache.wicket.Session;
27
import org.apache.wicket.protocol.http.WebApplication;
28
import org.apache.wicket.request.Request;
29
import org.apache.wicket.request.Response;
30
import org.apache.wicket.settings.ExceptionSettings;
31
import org.apache.wicket.settings.RequestCycleSettings;
32
import org.apache.wicket.util.lang.Bytes;
33
import org.nanopub.Nanopub;
34
import org.nanopub.extra.services.QueryRef;
35
import org.slf4j.Logger;
36
import org.slf4j.LoggerFactory;
37

38
import java.awt.*;
39
import java.io.IOException;
40
import java.io.InputStreamReader;
41
import java.lang.reflect.Type;
42
import java.net.URI;
43
import java.net.URISyntaxException;
44
import java.util.ArrayList;
45
import java.util.Collections;
46
import java.util.List;
47
import java.util.Properties;
48

49
/**
50
 * WicketApplication is the main application class for the Nanodash web application.
51
 * It initializes the application, mounts pages, and provides version information.
52
 */
53
public class WicketApplication extends WebApplication implements NanopubPublishedPublisher {
54

55
    /**
56
     * URL to fetch the latest release information from GitHub.
57
     * This URL points to the releases of the Nanodash repository.
58
     */
59
    public static final String LATEST_RELEASE_URL = "https://api.github.com/repos/knowledgepixels/nanodash/releases";
60
    private static final Logger logger = LoggerFactory.getLogger(WicketApplication.class);
9✔
61

62
    private final List<NanopubPublishedListener> publishListeners = Collections.synchronizedList(new ArrayList<>());
18✔
63

64
    private static String latestVersion = null;
6✔
65

66
    @Override
67
    public void registerListener(NanopubPublishedListener listener) {
68
        logger.info("Registering listener {} for nanopub published events", listener.getClass().getName());
18✔
69
        publishListeners.add(listener);
15✔
70
    }
3✔
71

72
    @Override
73
    public void notifyNanopubPublished(Nanopub nanopub, String target, long waitMs) {
74
        for (NanopubPublishedListener listener : publishListeners) {
×
75
            listener.onNanopubPublished(nanopub, target, waitMs);
×
76
            logger.info("Notifying listener {} with toRefresh target <{}>", listener.getClass().getName(), target);
×
77
        }
×
78
    }
×
79

80
    /**
81
     * Static method to get the current instance of the WicketApplication.
82
     *
83
     * @return The current instance of WicketApplication.
84
     */
85
    public static WicketApplication get() {
86
        return (WicketApplication) WebApplication.get();
×
87
    }
88

89
    /**
90
     * Constructor for the WicketApplication.
91
     * Displays version information and provides instructions for accessing the application.
92
     */
93
    public WicketApplication() {
6✔
94
        if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
6!
95
            try {
96
                Desktop.getDesktop().browse(new URI("http://localhost:37373"));
×
97
            } catch (IOException | URISyntaxException ex) {
×
98
                logger.error("Error in opening browser", ex);
×
99
            }
×
100
        }
101
        String v = getThisVersion();
6✔
102
        String lv = getLatestVersion();
6✔
103
        System.err.println("");
9✔
104
        System.err.println("----------------------------------------");
9✔
105
        System.err.println("               Nanodash");
9✔
106
        System.err.println("----------------------------------------");
9✔
107
        System.err.println(" You are using version: " + v);
12✔
108
        System.err.println(" Latest public version: " + lv);
12✔
109
        System.err.println("----------------------------------------");
9✔
110
        System.err.println(" Your browser should show the Nanodash");
9✔
111
        System.err.println(" interface in a few seconds.");
9✔
112
        System.err.println("");
9✔
113
        System.err.println(" If not, point your browser to:");
9✔
114
        System.err.println(" http://localhost:37373");
9✔
115
        System.err.println("----------------------------------------");
9✔
116
        System.err.println("");
9✔
117
    }
3✔
118

119
    /**
120
     * Returns the home page class for the application.
121
     *
122
     * @return The HomePage class.
123
     */
124
    public Class<HomePage> getHomePage() {
125
        return HomePage.class;
6✔
126
    }
127

128
    /**
129
     * {@inheritDoc}
130
     * <p>
131
     * Initializes the application settings and mounts pages.
132
     */
133
    @Override
134
    protected void init() {
135
        super.init();
6✔
136
        WicketWebjars.install(this);
6✔
137

138
        getMarkupSettings().setDefaultMarkupEncoding("UTF-8");
15✔
139
        getRequestCycleSettings().setRenderStrategy(RequestCycleSettings.RenderStrategy.ONE_PASS_RENDER);
15✔
140

141
        getExceptionSettings().setUnexpectedExceptionDisplay(ExceptionSettings.SHOW_NO_EXCEPTION_PAGE);
15✔
142

143
        mountPage(ErrorPage.MOUNT_PATH, ErrorPage.class);
15✔
144
        mountPage("/error/404", ErrorPage.class);
15✔
145
        mountPage("/error/500", ErrorPage.class);
15✔
146

147
        mountPage(UserPage.MOUNT_PATH, UserPage.class);
15✔
148
        mountPage(ChannelPage.MOUNT_PATH, ChannelPage.class);
15✔
149
        mountPage(SearchPage.MOUNT_PATH, SearchPage.class);
15✔
150
        mountPage(ExplorePage.MOUNT_PATH, ExplorePage.class);
15✔
151
        mountPage(ReferencesPage.MOUNT_PATH, ReferencesPage.class);
15✔
152
        mountPage(PublishPage.MOUNT_PATH, PublishPage.class);
15✔
153
        mountPage(PreviewPage.MOUNT_PATH, PreviewPage.class);
15✔
154
        mountPage(ProfilePage.MOUNT_PATH, ProfilePage.class);
15✔
155
        mountPage(UserListPage.MOUNT_PATH, UserListPage.class);
15✔
156
        mountPage(GroupDemoPage.MOUNT_PATH, GroupDemoPage.class);
15✔
157
        mountPage(GroupDemoPageSoc.MOUNT_PATH, GroupDemoPageSoc.class);
15✔
158
        mountPage(OrcidLinkingPage.MOUNT_PATH, OrcidLinkingPage.class);
15✔
159
        mountPage(OrcidLoginPage.MOUNT_PATH, OrcidLoginPage.class);
15✔
160
        mountPage(SpaceListPage.MOUNT_PATH, SpaceListPage.class);
15✔
161
        mountPage(MyChannelPage.MOUNT_PATH, MyChannelPage.class);
15✔
162
        mountPage(TermForwarder.MOUNT_PATH, TermForwarder.class);
15✔
163
        mountPage(ViewPage.MOUNT_PATH, ViewPage.class);
15✔
164
        mountPage(GetViewPage.MOUNT_PATH, GetViewPage.class);
15✔
165
        mountPage(DsOverviewPage.MOUNT_PATH, DsOverviewPage.class);
15✔
166
        mountPage(DsNanopubPage.MOUNT_PATH, DsNanopubPage.class);
15✔
167
        mountPage(RioOverviewPage.MOUNT_PATH, RioOverviewPage.class);
15✔
168
        mountPage(RioNanopubPage.MOUNT_PATH, RioNanopubPage.class);
15✔
169
        mountPage(BdjOverviewPage.MOUNT_PATH, BdjOverviewPage.class);
15✔
170
        mountPage(BdjNanopubPage.MOUNT_PATH, BdjNanopubPage.class);
15✔
171
        mountPage(FdoForwarder.MOUNT_PATH, FdoForwarder.class);
15✔
172
        mountPage(GetNamePage.MOUNT_PATH, GetNamePage.class);
15✔
173
        mountPage(TestPage.MOUNT_PATH, TestPage.class);
15✔
174
        mountPage(ResultTablePage.MOUNT_PATH, ResultTablePage.class);
15✔
175
        mountPage(GenOverviewPage.MOUNT_PATH, GenOverviewPage.class);
15✔
176
        mountPage(GenSelectPage.MOUNT_PATH, GenSelectPage.class);
15✔
177
        mountPage(GenPublishPage.MOUNT_PATH, GenPublishPage.class);
15✔
178
        mountPage(GenConnectPage.MOUNT_PATH, GenConnectPage.class);
15✔
179
        mountPage(GenNanopubPage.MOUNT_PATH, GenNanopubPage.class);
15✔
180
        mountPage(ProjectPage.MOUNT_PATH, ProjectPage.class);
15✔
181
        mountPage(SpacePage.MOUNT_PATH, SpacePage.class);
15✔
182
        mountPage(QueryPage.MOUNT_PATH, QueryPage.class);
15✔
183
        mountPage(QueryListPage.MOUNT_PATH, QueryListPage.class);
15✔
184
        mountPage(ListPage.MOUNT_PATH, ListPage.class);
15✔
185
        mountPage(MaintainedResourcePage.MOUNT_PATH, MaintainedResourcePage.class);
15✔
186
        mountPage(ResourcePartPage.MOUNT_PATH, ResourcePartPage.class);
15✔
187

188
        getCspSettings().blocking().disabled();
15✔
189
        getStoreSettings().setMaxSizePerSession(Bytes.MAX);
15✔
190

191
        registerListeners();
6✔
192

193
        String umamiScriptUrl = NanodashPreferences.get().getUmamiScriptUrl();
9✔
194
        if (umamiScriptUrl != null && !umamiScriptUrl.isBlank()) {
6!
195
            logger.info("Umami analytics configured: {}", umamiScriptUrl);
×
196
        } else {
197
            logger.info("Umami analytics not configured (set NANODASH_UMAMI_SCRIPT_URL and NANODASH_UMAMI_WEBSITE_ID)");
9✔
198
        }
199
    }
3✔
200

201
    /**
202
     * {@inheritDoc}
203
     * <p>
204
     * Returns the runtime configuration type for the application.
205
     */
206
    @Override
207
    public RuntimeConfigurationType getConfigurationType() {
208
        return RuntimeConfigurationType.DEPLOYMENT;
6✔
209
    }
210

211
    /**
212
     * Retrieves the latest version of the application from the GitHub API.
213
     *
214
     * @return The latest version as a string.
215
     */
216
    public static String getLatestVersion() {
217
        if (latestVersion != null) return latestVersion;
12✔
218
        try (CloseableHttpClient client = HttpClientBuilder.create().build()) {
9✔
219
            HttpResponse resp = client.execute(new HttpGet(LATEST_RELEASE_URL));
21✔
220
            int c = resp.getStatusLine().getStatusCode();
12✔
221
            if (c < 200 || c >= 300) {
18!
222
                throw new HttpStatusException(c);
×
223
            }
224

225
            Gson gson = new Gson();
12✔
226
            Type nanopubReleasesType = new TypeToken<List<NanodashRelease>>() {
18✔
227
            }.getType();
6✔
228

229
            try (InputStreamReader reader = new InputStreamReader(resp.getEntity().getContent())) {
21✔
230
                List<NanodashRelease> releases = gson.fromJson(reader, nanopubReleasesType);
18✔
231
                if (!releases.isEmpty()) {
9!
232
                    latestVersion = releases.getFirst().getVersionNumber();
15✔
233
                }
234
            }
235
        } catch (Exception ex) {
×
236
            logger.error("Error in fetching latest version", ex);
×
237
        }
3✔
238
        return latestVersion;
6✔
239
    }
240

241
    /**
242
     * Properties object to hold application properties.
243
     */
244
    public final static Properties properties = new Properties();
12✔
245

246
    static {
247
        try {
248
            properties.load(WicketApplication.class.getClassLoader().getResourceAsStream("nanodash.properties"));
18✔
249
        } catch (IOException ex) {
×
250
            logger.error("Error in loading properties", ex);
×
251
        }
3✔
252
    }
3✔
253

254
    /**
255
     * Retrieves the current version of the application.
256
     *
257
     * @return The current version as a string.
258
     */
259
    public static String getThisVersion() {
260
        return properties.getProperty("nanodash.version");
12✔
261
    }
262

263
    /**
264
     * {@inheritDoc}
265
     */
266
    @Override
267
    public Session newSession(Request request, Response response) {
268
        return new NanodashSession(request);
15✔
269
    }
270

271
    private void registerListeners() {
272
        logger.info("Registering nanopub published event listeners for spaces, maintained resources, resource with profile and query ref refresh");
9✔
273
        registerListener((nanopub, target, waitMs) -> {
9✔
274
            logger.info("Received nanopub published event with target <{}> and waitMs {}", target, waitMs);
×
275
            if (target.equals("spaces")) {
×
276
                SpaceRepository.get().forceRootRefresh(waitMs);
×
277
            } else if (target.equals("maintainedResources")) {
×
278
                MaintainedResourceRepository.get().forceRootRefresh(waitMs);
×
279
            } else if (AbstractResourceWithProfile.isResourceWithProfile(target)) {
×
280
                AbstractResourceWithProfile resource = AbstractResourceWithProfile.get(target);
×
281
                resource.forceRefresh(waitMs);
×
282
                if (resource instanceof Space) {
×
283
                    SpaceRepository.get().forceRootRefresh(waitMs);
×
284
                    MaintainedResourceRepository.get().forceRootRefresh(waitMs);
×
285
                } else if (resource instanceof MaintainedResource) {
×
286
                    MaintainedResourceRepository.get().forceRootRefresh(waitMs);
×
287
                }
288
            } else {
×
289
                QueryRef queryRef = QueryRef.parseString(target);
×
290
                ApiCache.clearCache(queryRef, waitMs);
×
291
            }
292
        });
×
293
    }
3✔
294

295
}
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