This document outlines practical refactoring opportunities to apply well-known Gang of Four design patterns to improve the Wikantik codebase architecture.
Based on comprehensive analysis of the Wikantik codebase, six high-value refactoring opportunities have been identified. These patterns would improve code maintainability, testability, and extensibility.
CachingProvider wraps PageProvider but this pattern isn't generalized.
Code Location: wikantik-main/src/main/java/org/apache/wiki/providers/CachingProvider.java:63-102
CachingAttachmentProvider)// Base decorator for PageProvider
public abstract class PageProviderDecorator implements PageProvider {
protected final PageProvider delegate;
protected PageProviderDecorator(PageProvider delegate) {
this.delegate = Objects.requireNonNull(delegate);
}
@Override
public void putPageText(Page page, String text) throws ProviderException {
delegate.putPageText(page, text);
}
// ... delegate all other methods by default
}
// Specific decorators
public class CachingPageProviderDecorator extends PageProviderDecorator { ... }
public class MetricsPageProviderDecorator extends PageProviderDecorator { ... }
public class LoggingPageProviderDecorator extends PageProviderDecorator { ... }
new Caching(new Logging(new FileSystem()))Effort: Medium | Impact: High
Providers are instantiated independently via ClassUtil.buildInstance():
PageManager creates its PageProviderAttachmentManager creates its AttachmentProviderSearchManager creates its SearchProviderCode Location: wikantik-main/src/main/java/org/apache/wiki/WikiEngine.java:272-310
public interface StorageBackendFactory {
PageProvider createPageProvider(Engine engine, Properties props);
AttachmentProvider createAttachmentProvider(Engine engine, Properties props);
SearchProvider createSearchProvider(Engine engine, Properties props);
// Factory method to get appropriate factory
static StorageBackendFactory forBackend(String backendType) {
return switch(backendType) {
case "filesystem" -> new FileSystemStorageFactory();
case "jdbc" -> new JdbcStorageFactory();
case "versioning" -> new VersioningFileStorageFactory();
default -> throw new IllegalArgumentException("Unknown backend: " + backendType);
};
}
}
public class VersioningFileStorageFactory implements StorageBackendFactory {
@Override
public PageProvider createPageProvider(Engine engine, Properties props) {
return new VersioningFileProvider();
}
@Override
public AttachmentProvider createAttachmentProvider(Engine engine, Properties props) {
return new BasicAttachmentProvider();
}
@Override
public SearchProvider createSearchProvider(Engine engine, Properties props) {
return new LuceneSearchProvider();
}
}
Effort: Medium | Impact: High
WikiEngine.initialize() has 25+ sequential initComponent() calls with implicit ordering:
initComponent( CommandResolver.class, this, props );
initComponent( CachingManager.class, this, props );
initComponent( PageManager.class, this, props );
// ... 20+ more
Code Location: wikantik-main/src/main/java/org/apache/wiki/WikiEngine.java:272-310
public class WikiEngineBuilder {
private final Map<Class<?>, ManagerConfig> managerConfigs = new LinkedHashMap<>();
public WikiEngineBuilder withManager(Class<?> managerClass, Class<?>... dependsOn) {
managerConfigs.put(managerClass, new ManagerConfig(managerClass, dependsOn));
return this;
}
public WikiEngine build(Properties props) throws WikiException {
// Topological sort based on dependencies
List<Class<?>> initOrder = resolveDependencyOrder(managerConfigs);
WikiEngine engine = new WikiEngine();
for (Class<?> manager : initOrder) {
engine.initComponent(manager, props);
}
return engine;
}
}
// Usage
WikiEngine engine = new WikiEngineBuilder()
.withManager(CachingManager.class)
.withManager(PageManager.class, CachingManager.class)
.withManager(FilterManager.class, PageManager.class)
.withManager(RenderingManager.class, FilterManager.class)
.build(props);
Effort: High | Impact: Medium
VersioningFileProvider.CachedProperties is a single-entry cache:
private static class CachedProperties {
String m_page;
Properties m_props;
long m_lastModified;
}
private CachedProperties m_cachedProperties; // Only ONE entry!
Code Location: wikantik-main/src/main/java/org/apache/wiki/providers/VersioningFileProvider.java:697-720
public interface PropertyCacheStrategy {
Properties get(String page, Supplier<Properties> loader);
void invalidate(String page);
void clear();
}
// Single-entry (current behavior)
public class SingleEntryPropertyCache implements PropertyCacheStrategy { ... }
// LRU cache (new)
public class LruPropertyCache implements PropertyCacheStrategy {
private final Map<String, CachedProperties> cache;
private final int maxSize;
// Uses LinkedHashMap with removeEldestEntry()
}
// No-op cache (for testing)
public class NoOpPropertyCache implements PropertyCacheStrategy {
@Override
public Properties get(String page, Supplier<Properties> loader) {
return loader.get();
}
}
Effort: Low | Impact: Medium (synergizes with disk I/O optimizations)
Events use integer constants:
public abstract class WikiEvent extends EventObject {
public static final int ERROR = -99;
public static final int UNDEFINED = -98;
private int m_type = UNDEFINED;
}
// In WikiPageEvent
public static final int PAGE_LOCK = 11;
public static final int PAGE_UNLOCK = 12;
public static final int PAGE_REQUESTED = 20;
Code Location: jspwiki-event/src/main/java/org/apache/wiki/event/WikiEvent.java
switch statements on int values scattered through codebase// Sealed event hierarchy (Java 17+)
public sealed interface WikiEvent permits
PageEvent, SecurityEvent, EngineEvent, WorkflowEvent {
Object getSource();
long getWhen();
<T> T accept(WikiEventVisitor<T> visitor);
}
public sealed interface PageEvent extends WikiEvent permits
PageLockEvent, PageUnlockEvent, PageSaveEvent, PageDeleteEvent {
String getPageName();
}
public record PageSaveEvent(Object source, long when, String pageName, int version)
implements PageEvent {
@Override
public <T> T accept(WikiEventVisitor<T> visitor) {
return visitor.visitPageSave(this);
}
}
// Type-safe visitor
public interface WikiEventVisitor<T> {
T visitPageSave(PageSaveEvent event);
T visitPageDelete(PageDeleteEvent event);
// ...
}
// Type-safe listener
public interface PageEventListener {
void onPageSave(PageSaveEvent event);
void onPageDelete(PageDeleteEvent event);
}
Effort: High | Impact: Medium (modernization)
All providers implement initialize() but lifecycle is inconsistent:
public interface WikiProvider {
void initialize(Engine engine, Properties properties) throws ...;
String getProviderInfo();
}
public abstract class AbstractWikiProvider implements WikiProvider {
private Engine engine;
private Properties properties;
private boolean initialized;
@Override
public final void initialize(Engine engine, Properties properties)
throws WikiException {
this.engine = engine;
this.properties = properties;
validateConfiguration(properties); // Hook
doInitialize(engine, properties); // Hook
postInitialize(); // Hook
initialized = true;
}
public final void shutdown() {
if (!initialized) return;
doShutdown(); // Hook
initialized = false;
}
public final void reload() throws WikiException {
shutdown();
initialize(engine, properties);
}
// Template methods for subclasses
protected void validateConfiguration(Properties props) throws WikiException {}
protected abstract void doInitialize(Engine engine, Properties props) throws WikiException;
protected void postInitialize() {}
protected void doShutdown() {}
}
Effort: Medium | Impact: Medium
| Priority | Pattern | Effort | Impact | Dependencies |
|---|---|---|---|---|
| 1 | Decorator (Providers) | Medium | High | None |
| 2 | Abstract Factory (Storage) | Medium | High | None |
| 3 | Builder (Engine Init) | High | Medium | None |
| 4 | Strategy (Property Cache) | Low | Medium | None |
| 5 | Type-Safe Events | High | Medium | None |
| 6 | Template Method (Lifecycle) | Medium | Medium | None |
Priority 1 + 4 together would significantly improve the provider architecture with moderate effort.
The codebase already has excellent implementations of several patterns:
These should be maintained and can serve as examples for new pattern implementations.