{"record":{"id":"e2a7bbcfa56e84c8","repo":"theonedev/onedev","slug":"page-classes-should-extend-from-basepage","errorCode":null,"errorMessage":"Page classes should extend from BasePage.","messagePattern":"Page classes should extend from BasePage\\.","errorType":"exception","errorClass":"ExplicitException","httpStatus":null,"severity":"error","filePath":"server-core/src/main/java/io/onedev/server/web/WebApplication.java","lineNumber":174,"sourceCode":"\t\t * a user visits the page, the page instance may not get written to disk timely due to\n\t\t * page synchronous writing. So adding an in-memory cache is important to compensate the\n\t\t * page written latency; otherwise, user may experience odd exceptions such as \n\t\t * ComponentNotFound when visit a page instance again after it is being created\n\t\t */\n\t\tgetStoreSettings().setInmemoryCacheSize(1000);\n\t\t\n\t\tgetRequestCycleSettings().setTimeout(Duration.minutes(30));\n\t\t\n\t\tgetComponentInstantiationListeners().add(new GuiceComponentInjector(this, AppLoader.injector));\n\t\tgetComponentInstantiationListeners().add(new IComponentInstantiationListener() {\n\t\t\t\n\t\t\t@Override\n\t\t\tpublic void onInstantiation(Component component) {\n\t\t\t\tif ((component instanceof Page) \n\t\t\t\t\t\t&& !(component instanceof AbstractErrorPage) \n\t\t\t\t\t\t&& !(component instanceof BasePage)\n\t\t\t\t\t\t&& !(component instanceof BrowserInfoPage)) {\n\t\t\t\t\tthrow new ExplicitException(\"Page classes should extend from BasePage.\");\n\t\t\t\t} else if (component instanceof Link && !(component instanceof BookmarkablePageLink)) {\n\t\t\t\t\tcomponent.add(AttributeAppender.append(\"rel\", \"nofollow\"));\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\t\n\t\tgetSessionListeners().add(new ISessionListener() {\n\n\t\t\t@Override\n\t\t\tpublic void onCreated(Session session) {\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void onUnbound(String sessionId) {\n\t\t\t}\n\t\t\t\t\t\t\n\t\t});\n\t\tgetAjaxRequestTargetListeners().add(new AjaxRequestTarget.IListener() {","sourceCodeStart":156,"sourceCodeEnd":192,"githubUrl":"https://github.com/theonedev/onedev/blob/d44925c47c37992c828ea673a5f9620539bc3ff2/server-core/src/main/java/io/onedev/server/web/WebApplication.java#L156-L192","documentation":"OneDev's WebApplication installs an Wicket onInstantiation listener that enforces every Page component instantiated in the application extends BasePage (with narrow exceptions for AbstractErrorPage and BrowserInfoPage). This guarantees consistent page chrome, security checks, and layout. If a custom Page class is registered that bypasses BasePage, the framework throws this ExplicitException immediately at instantiation time.","triggerScenarios":"Registering or navigating to a Wicket Page class that directly extends org.apache.wicket.markup.html.WebPage (or another Page base) but not io.onedev.server.web.page.base.BasePage, and is not an AbstractErrorPage or BrowserInfoPage subclass.","commonSituations":"Developers adding a new custom page/plugin page and extending WebPage out of habit; copying a plain Wicket example page into a OneDev plugin; refactoring a page to no longer extend BasePage.","solutions":["Make the page class extend io.onedev.server.web.page.base.BasePage (or a subclass like BasePageContent or a layout page) instead of WebPage.","If the page is intentionally an error page, extend AbstractErrorPage instead.","If the page only handles browser-info handshakes, use/extend BrowserInfoPage which is whitelisted."],"exampleFix":"// before\npublic class MyCustomPage extends WebPage { ... }\n\n// after\nimport io.onedev.server.web.page.base.BasePage;\npublic class MyCustomPage extends BasePage { ... }","handlingStrategy":"validation","validationCode":"if (Page.class.isAssignableFrom(pageClass)\n        && !AbstractErrorPage.class.isAssignableFrom(pageClass)\n        && !BasePage.class.isAssignableFrom(pageClass)\n        && !BrowserInfoPage.class.isAssignableFrom(pageClass)) {\n    throw new IllegalStateException(pageClass + \" must extend BasePage\");\n}","typeGuard":"static boolean isAllowedPage(Component c) {\n    return !(c instanceof Page) || c instanceof AbstractErrorPage\n        || c instanceof BasePage || c instanceof BrowserInfoPage;\n}","tryCatchPattern":null,"preventionTips":["Always extend BasePage (or an existing OneDev page subclass) when creating new pages.","Add a unit test that instantiates every registered page class to catch violations at build time.","Search the plugin for 'extends WebPage' during code review."],"tags":["wicket","page-hierarchy","framework-convention"],"backgroundTag":"invalid-argument-value","analyzedSha":"d44925c47c37992c828ea673a5f9620539bc3ff2","analyzedAt":"2026-09-06T07:18:27.995Z","contentChangedAt":"2026-09-06T07:18:27.995Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}