Implementing real-time collaboration and document editing in Apache Wicket

Apache Wicket is a powerful Java web framework that allows developers to build scalable and maintainable web applications. In this blog post, we will explore how to implement real-time collaboration and document editing features using Apache Wicket.

Why Real-time Collaboration?

Real-time collaboration allows multiple users to work on a document simultaneously, providing a more efficient and interactive experience. Implementing real-time collaboration in Apache Wicket can enhance the usability and productivity of your web application.

Setting Up the Project

To get started, you will need to set up a new Apache Wicket project. You can use a Maven-based setup or any other preferred method. Once you have your project structure in place, follow the steps below to implement real-time collaboration.

Step 1: Set Up WebSocket Support

To enable real-time communication, we need to add WebSocket support to our Apache Wicket project. Apache Wicket provides built-in support for the WebSocket API. Add the necessary dependencies to your project’s pom.xml file:

<dependency>
    <groupId>org.apache.wicket</groupId>
    <artifactId>wicket-native-websocket-javax</artifactId>
    <version>9.3.0</version>
</dependency>

Step 2: Create Document Editing Component

Next, we need to create a component that allows users to edit documents. This component will use the WebSocket API for real-time collaboration.

import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.form.TextArea;
import org.apache.wicket.model.IModel;

public class DocumentEditor extends WebMarkupContainer {
  
    private TextArea<String> textArea;
    private WebSocketConnection webSocketConnection;

    public DocumentEditor(String id) {
        super(id);
        textArea = new TextArea<>("textArea");
        add(textArea);
    }
    
    @Override
    protected void onBeforeRender() {
        super.onBeforeRender();
        webSocketConnection.send(textArea.getModelObject());
    }
    
    public void setWebSocketConnection(WebSocketConnection connection) {
        this.webSocketConnection = connection;
    }
}

The DocumentEditor component includes a TextArea to capture user input. The onBeforeRender method is overridden to send the text content to the connected WebSocket server.

Step 3: Configure WebSocket Connection

To establish a connection with the WebSocket server, we need to configure the WebSocket connection in our Apache Wicket application.

import org.apache.wicket.protocol.ws.api.IWebSocketConnectionRegistry;
import org.apache.wicket.protocol.ws.api.WebSocketBehavior;
import org.apache.wicket.protocol.ws.api.WebSocketSettings;
import org.apache.wicket.protocol.ws.api.registry.IKey;
import org.apache.wicket.protocol.ws.api.registry.SimpleKey;
import org.apache.wicket.protocol.ws.api.registry.mapping.IKeyToApplicationMapper;
import org.apache.wicket.protocol.ws.api.registry.mapping.IWebSocketRequestHandler;
import org.apache.wicket.protocol.ws.api.registry.mapping.IWebSocketRequestMapper;
import org.apache.wicket.protocol.ws.api.registry.mapping.StatelessIndexedParamUrlWebSocketRequestMapper;
import org.apache.wicket.protocol.ws.api.registry.mapping.UrlWebSocketRequestMapper;
import org.apache.wicket.spring.injection.annot.SpringBean;

public class MyApplication extends WebApplication {

    @SpringBean
    private IWebSocketConnectionRegistry webSocketConnectionRegistry;

    @Override
    protected void init() {
        super.init();
        
        WebSocketSettings webSocketSettings = WebSocketSettings.Holder.get(this);
        webSocketSettings.setConnectionInitializer(new WebSocketBehavior() {
            @Override
            protected void onConnect(ConnectedMessage message) {
                IKey key = new SimpleKey(MyApplication.class.getName(), message.getSessionId());
                webSocketConnectionRegistry.put(key, new WebSocketConnection());
            }
        });

        IWebSocketRequestMapper requestMapper = new UrlWebSocketRequestMapper(
            new LightweightWebSocketResource(this));
        
        webSocketSettings.setRequestMappings(Arrays.asList(requestMapper));
    }
}

The WebSocket connection is established in the MyApplication class. We use the UrlWebSocketRequestMapper to map WebSocket requests to our custom LightweightWebSocketResource class.

Step 4: Implement Server-side WebSocket

Create a server-side WebSocket class that handles the incoming WebSocket messages:

import org.apache.wicket.protocol.ws.api.IWebSocketConnectionRegistry;
import org.apache.wicket.protocol.ws.api.registry.IKey;
import org.apache.wicket.protocol.ws.api.registry.SimpleKey;
import org.apache.wicket.protocol.ws.api.registry.IWebSocketConnection;
import org.apache.wicket.protocol.ws.api.registry.IWebSocketConnectionFilter;
import org.apache.wicket.protocol.ws.api.registry.IWebSocketConnectionFilterFactory;

public class WebSocketConnection extends WebSocketBehavior implements IWebSocketConnection {
  
    private static final IWebSocketConnectionFilter FILTER = new IWebSocketConnectionFilter() {
        @Override
        public boolean isConnectionAllowed(IWebSocketConnection connection) {
            return true;
        }
    };

    private IWebSocketConnectionRegistry webSocketConnectionRegistry;

    public WebSocketConnection() {
        super();
    }
  
    @Override
    protected void onPush(WebSocketRequestHandler handler, IWebSocketPushMessage message) {
        webSocketConnectionRegistry.broadcast(FILTER, message);
    }
  
    public void setWebSocketConnectionRegistry(IWebSocketConnectionRegistry connectionRegistry) {
        this.webSocketConnectionRegistry = connectionRegistry;
    }

    public void send(String message) {
        IKey key = new SimpleKey(MyApplication.class.getName(), getSession().getId());
        webSocketConnectionRegistry.getConnections(key, FILTER)
            .forEach(connection -> connection.sendMessage(message));
    }
}

The WebSocketConnection class extends WebSocketBehavior and implements IWebSocketConnection. Here, we implement the onPush method to broadcast incoming messages to all connected clients, and the send method to send messages from the document editor component to the server.

Conclusion

In this blog post, we explored the process of implementing real-time collaboration and document editing features using Apache Wicket. By adding WebSocket support and creating custom components and WebSocket classes, we can enable real-time communication and enhance the collaboration capabilities of our Apache Wicket application.

#ApacheWicket #RealTimeCollaboration