A sitemap.xml tells search engines about the pages on your wiki, helping them crawl more efficiently. This document provides a detailed breakdown of what needs to be implemented for Google Search Console compliance.
Google Search Console expects sitemaps to follow the Sitemap Protocol
:
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://wiki.example.com/wiki/Main</loc>
<lastmod>2025-11-22</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
<!-- more URLs -->
</urlset>
Key requirements:
Location: wikantik-main/src/main/java/org/apache/wiki/ui/SitemapServlet.java
package com.wikantik.ui;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import jakarta.servlet.ServletConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.wikantik.api.core.Context;
import com.wikantik.api.core.ContextEnum;
import com.wikantik.api.core.Engine;
import com.wikantik.api.core.Page;
import com.wikantik.api.core.Session;
import com.wikantik.api.spi.Wiki;
import com.wikantik.auth.AuthorizationManager;
import com.wikantik.auth.permissions.PagePermission;
import com.wikantik.pages.PageManager;
import com.wikantik.references.ReferenceManager;
import com.wikantik.url.URLConstructor;
public class SitemapServlet extends HttpServlet {
private static final Logger LOG = LogManager.getLogger( SitemapServlet.class );
private Engine m_engine;
@Override
public void init( final ServletConfig config ) throws ServletException {
super.init( config );
m_engine = Wiki.engine().find( config );
LOG.info( "SitemapServlet initialized." );
}
@Override
protected void doGet( final HttpServletRequest req, final HttpServletResponse resp )
throws ServletException, IOException {
// Generate sitemap XML
}
}
1. 2.1 Get All Pages
PageManager pageManager = m_engine.getManager( PageManager.class );
Collection<Page> allPages = pageManager.getAllPages();
1. 2.2 Filter Pages Based on Permissions
Only include pages that are publicly accessible (anonymous users can view):
AuthorizationManager authManager = m_engine.getManager( AuthorizationManager.class );
Session session = Wiki.session().find( m_engine, req );
List<Page> publicPages = allPages.stream()
.filter( page -> {
try {
PagePermission permission = new PagePermission( page, PagePermission.VIEW_ACTION );
// For sitemap, we want pages viewable by anonymous users
// Create a context to check permissions
Context context = Wiki.context().create( m_engine, req, ContextEnum.PAGE_VIEW.getRequestContext() );
context.setPage( page );
return authManager.checkPermission( context.getWikiSession(), permission );
} catch ( Exception e ) {
return false;
}
} )
.collect( Collectors.toList() );
1. 2.3 Generate URLs for Each Page
Use the URLConstructor to generate proper URLs based on the wiki's configuration:
URLConstructor urlConstructor = m_engine.getManager( URLConstructor.class );
String baseUrl = m_engine.getBaseURL();
for ( Page page : publicPages ) {
// Use URLConstructor to generate URLs in the configured format
String pageUrl = baseUrl + urlConstructor.makeURL(
ContextEnum.PAGE_VIEW.getRequestContext(),
page.getName(),
null
);
// Remove any leading slash duplication
pageUrl = pageUrl.replace( baseUrl + "/" + baseUrl, baseUrl );
Date lastModified = page.getLastModified();
// Write to XML
}
1. 2.4 Write XML Output
resp.setContentType( "application/xml" );
resp.setCharacterEncoding( "UTF-8" );
PrintWriter out = resp.getWriter();
out.println( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" );
out.println( "<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">" );
SimpleDateFormat dateFormat = new SimpleDateFormat( "yyyy-MM-dd" );
for ( Page page : publicPages ) {
String pageUrl = m_engine.getManager( URLConstructor.class )
.makeURL( ContextEnum.PAGE_VIEW.getRequestContext(), page.getName(), null );
// Make URL absolute
if ( !pageUrl.startsWith( "http" ) ) {
pageUrl = m_engine.getBaseURL() + pageUrl;
}
out.println( " <url>" );
out.println( " <loc>" + escapeXml( pageUrl ) + "</loc>" );
if ( page.getLastModified() != null ) {
out.println( " <lastmod>" + dateFormat.format( page.getLastModified() ) + "</lastmod>" );
}
out.println( " <changefreq>" + determineChangeFreq( page ) + "</changefreq>" );
out.println( " <priority>" + determinePriority( page ) + "</priority>" );
out.println( " </url>" );
}
out.println( "</urlset>" );
1. 3.1 Determine Change Frequency
Based on page edit history:
private String determineChangeFreq( final Page page ) {
PageManager pm = m_engine.getManager( PageManager.class );
List<Page> history = pm.getVersionHistory( page.getName() );
if ( history == null || history.size() <= 1 ) {
return "monthly";
}
// Calculate days since last edit
long daysSinceLastEdit = java.time.temporal.ChronoUnit.DAYS.between(
page.getLastModified().toInstant(), java.time.Instant.now() );
if ( daysSinceLastEdit < 1 ) return "daily";
if ( daysSinceLastEdit < 7 ) return "weekly";
if ( daysSinceLastEdit < 30 ) return "monthly";
return "yearly";
}
1. 3.2 Determine Priority
Based on page importance:
private String determinePriority( final Page page ) {
String pageName = page.getName();
// Main page gets highest priority
if ( pageName.equals( m_engine.getFrontPage() ) ) {
return "1.0";
}
// Consider number of incoming links
ReferenceManager refManager = m_engine.getManager( ReferenceManager.class );
Collection<String> referrers = refManager.findReferrers( pageName );
if ( referrers != null && referrers.size() > 10 ) {
return "0.8";
} else if ( referrers != null && referrers.size() > 5 ) {
return "0.6";
}
return "0.5"; // Default priority
}
1. 3.3 XML Escaping
private String escapeXml( final String input ) {
return input
.replace( "&", "&" )
.replace( "<", "<" )
.replace( ">", ">" )
.replace( "\"", """ )
.replace( "'", "'" );
}
Location: wikantik-war/src/main/webapp/WEB-INF/web.xml
<servlet>
<servlet-name>SitemapServlet</servlet-name>
<servlet-class>com.wikantik.ui.SitemapServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>SitemapServlet</servlet-name>
<url-pattern>/sitemap.xml</url-pattern>
</servlet-mapping>
Location: wikantik-main/src/test/java/org/apache/wiki/ui/SitemapServletTest.java
Create tests for:
For performance, cache the generated sitemap using Wikantik's CachingManager:
private static final String CACHE_SITEMAP = "wikantik.sitemapCache";
private static final String CACHE_KEY = "sitemap";
@Override
protected void doGet( final HttpServletRequest req, final HttpServletResponse resp )
throws ServletException, IOException {
CachingManager cachingManager = m_engine.getManager( CachingManager.class );
String sitemap = cachingManager.get( CACHE_SITEMAP, CACHE_KEY, () -> generateSitemapContent( req ) );
resp.setContentType( "application/xml" );
resp.setCharacterEncoding( "UTF-8" );
resp.getWriter().write( sitemap );
}
private String generateSitemapContent( final HttpServletRequest req ) {
// Generate sitemap XML as a String
// ...
}
Note: You may need to register the cache name in the caching configuration.
Location: Add to wikantik-main/src/main/resources/ini/wikantik.properties
# Sitemap configuration
jspwiki.sitemap.enabled = true
jspwiki.sitemap.includeAttachments = false
jspwiki.sitemap.cacheTimeout = 3600
jspwiki.sitemap.excludePatterns = Admin*,Test*
jspwiki.sitemap.defaultChangeFreq = weekly
jspwiki.sitemap.defaultPriority = 0.5
Read configuration in the servlet:
private boolean isEnabled() {
return TextUtil.getBooleanProperty(
m_engine.getWikiProperties(),
"wikantik.sitemap.enabled",
true
);
}
For wikis with more than 50,000 pages, implement a sitemap index:
1. 8.1 Sitemap Index Format
<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap>
<loc>https://wiki.example.com/sitemap/1</loc>
<lastmod>2025-11-22</lastmod>
</sitemap>
<sitemap>
<loc>https://wiki.example.com/sitemap/2</loc>
<lastmod>2025-11-22</lastmod>
</sitemap>
</sitemapindex>
1. 8.2 URL Routing
Add additional servlet mapping in web.xml:
<servlet-mapping>
<servlet-name>SitemapServlet</servlet-name>
<url-pattern>/sitemap/*</url-pattern>
</servlet-mapping>
Implementation:
private static final int MAX_URLS_PER_SITEMAP = 50000;
@Override
protected void doGet( final HttpServletRequest req, final HttpServletResponse resp )
throws ServletException, IOException {
String pathInfo = req.getPathInfo(); // e.g., "/1" for sitemap chunk 1
PageManager pageManager = m_engine.getManager( PageManager.class );
int totalPages = pageManager.getTotalPageCount();
if ( pathInfo == null || pathInfo.equals( "/" ) ) {
// Check if we need sitemap index
if ( totalPages > MAX_URLS_PER_SITEMAP ) {
generateSitemapIndex( resp, totalPages );
} else {
generateSitemap( resp, req, 0, totalPages );
}
} else {
// Generate specific sitemap chunk
int chunk = Integer.parseInt( pathInfo.substring( 1 ) );
generateSitemap( resp, req, chunk * MAX_URLS_PER_SITEMAP, MAX_URLS_PER_SITEMAP );
}
}
To index attachments:
AttachmentManager attachmentManager = m_engine.getManager( AttachmentManager.class );
for ( Page page : publicPages ) {
// Add page URL...
// Add attachment URLs
if ( includeAttachments ) {
Collection<Attachment> attachments = attachmentManager.listAttachments( page );
for ( Attachment att : attachments ) {
String attUrl = m_engine.getManager( URLConstructor.class )
.makeURL( ContextEnum.PAGE_ATTACH.getRequestContext(), att.getName(), null );
// Write attachment URL to sitemap
}
}
}
Location: wikantik-war/src/main/webapp/robots.txt
User-agent: *
Allow: /
Sitemap: https://wiki.example.com/sitemap.xml
Note: The sitemap URL should be configurable or dynamically generated based on engine.getBaseURL().
| File | Action | Phase | Description |
|---|---|---|---|
wikantik-main/.../ui/SitemapServlet.java | Create | 1 | Main servlet implementation |
wikantik-war/.../WEB-INF/web.xml | Modify | 1 | Register servlet mapping |
wikantik-main/.../ui/SitemapServletTest.java | Create | 1 | Unit tests |
wikantik-main/.../ini/wikantik.properties | Modify | 2 | Add configuration properties |
wikantik-war/.../robots.txt | Create/Modify | 4 | Add sitemap reference |
URL Format: The implementation uses URLConstructor to generate URLs in the configured format (e.g., /wiki/PageName for ShortViewURLConstructor)
HTTP Headers: Add Last-Modified and ETag headers for efficient caching by search engines
Compression: Support gzip compression for large sitemaps (sitemap.xml.gz)
Internationalization: If your wiki has multiple language versions, consider using hreflang annotations
News Sitemaps: If your wiki has time-sensitive content, consider implementing Google News sitemap extensions
Wiki.engine().find( config )Wiki.session().find( engine, request )Wiki.context().create( engine, request, contextEnum )engine.getManager( URLConstructor.class ).makeURL( context, name, params )engine.getManager( AuthorizationManager.class ).checkPermission( session, permission )engine.getManager( CachingManager.class ).get( cacheName, key, supplier )engine.getManager( PageManager.class ).getAllPages()engine.getManager( ReferenceManager.class ).findReferrers( pageName )Use Jakarta EE (not javax):
import jakarta.servlet.ServletConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;