Extending the output instance filter

Last modified by Raphaël Jakse on 2025/07/29 08:15

Extending the Instance Output Filter Stream means adding new events that can be sent from input filter streams, and implementing these events in an output filter stream that will be automatically used by the Instance Output Filter Stream.

To do this:

  • Create a private filter interface that lists all the events your output filter stream takes.
  • Implement the output filter stream that implements this private filter interface
  • Implement the output filter stream factory.

These steps allow implementing a filter that takes no parameters. We describe supporting properties after detailing these steps.

Create the filter interface

Your interface will likely be located in some internal -default module. It will likely be named like XXXInstanceOutputFilter, have an empty body, and extend other interfaces including a public XXXFilter interface which defines your specific events and which lives in some public -api module. This last interface will be the one other filter streams will be able to import to produce the events you need to receive.

Example

For instance, here is an interface to implement a filter that imports favorites. 

package org.xwiki.contrib.favorites.internal.filter.output;

import org.xwiki.filter.event.model.WikiDocumentFilter;
import org.xwiki.filter.event.model.WikiFilter;
import org.xwiki.filter.event.model.WikiSpaceFilter;
import org.xwiki.contrib.favorites.filter.FavoriteFilter;

public interface FavoriteInstanceOutputFilter extends FavoriteFilter, WikiFilter, WikiDocumentFilter, WikiSpaceFilter
{
}

FavoriteFilter is a public interface living which defines one event:

package org.xwiki.contrib.favorites.filter;

import org.xwiki.filter.FilterEventParameters;
import org.xwiki.filter.FilterException;
import org.xwiki.filter.annotation.Default;
import org.xwiki.model.reference.EntityReference;
import org.xwiki.user.UserReference;

/**
 * Favorite related events.
 */
public interface FavoriteFilter
{
    // Events

    /**
     * This event puts the current entity as a favorite for the given user.
     * @param userRef the reference of the user
     * @param parameters parameters of the events (not yet used)
     * @throws FilterException if something wrong happens
     */
    void onUserFavorite(UserReference userRef, @Default("") FilterEventParameters throws FilterException;
}

Implement the filter

Your filter will implement OutputFilterStream, which defines a getFilter() method and is a Closeable, and the private you defined in the previous section. Your implementation of getFilter should just return this. You'll also implement a close method that will be called when the filtering is done. This method can remain empty if nothing is to be closed on your side.

Your events should allow throwing FilterException.

Example

class FavoriteInstanceOutputFilterStream implements FavoriteInstanceOutputFilter, OutputFilterStream
{
    private EntityReference currentReference;
    private final FavoriteManager favoriteManager;
    private List<EntityReference> favorites;

    FavoriteInstanceOutputFilterStream(FavoriteManager favoriteManager)
    {
        this.favoriteManager = favoriteManager;
    }

    @Override
    public Object getFilter()
    {
        return this;
    }

    @Override
    public void close() throws IOException
    {
        // Nothing to close
    }

    @Override
    public void onUserFavorite(UserReference userRef, FilterEventParameters parameters) throws FilterException
    {
        if (currentReference != null) {
            try {
                favoriteManager.add(userRef, currentReference);
            } catch (FavoritesException e) {
                throw new FilterException(e);
            }
        }
    }

    @Override
    public void beginWikiDocument(String name, FilterEventParameters parameters)
    {
        currentReference = new EntityReference(name, EntityType.DOCUMENT, currentReference);
    }

    @Override
    public void endWikiDocument(String name, FilterEventParameters parameters) throws FilterException
    {
        backToParent(name);
    }


    @Override
    public void beginWiki(String name, FilterEventParameters parameters)
    {
        currentReference = new WikiReference(name);
    }

    @Override
    public void endWiki(String name, FilterEventParameters parameters)
    {
        currentReference = null;
    }

    @Override
    public void beginWikiSpace(String name, FilterEventParameters parameters)
    {
        currentReference = new EntityReference(name, EntityType.SPACE, currentReference);
    }

    @Override
    public void endWikiSpace(String name, FilterEventParameters parameters)
    {
        backToParent(name);
    }

    private void backToParent(String name)
    {
        if (currentReference != null && (name == null || currentReference.getName().equals(name))) {
            currentReference = currentReference.getParent();
        }
    }
}

Implement the filter factory

Your output filter stream is not imported directly. Actually, it's not even necessarily a component. Your factory is in charge of instanciating it.

Implement org.xwiki.filter.instance.output.OutputInstanceFilterStreamFactory as a component. The Instance Output Filter Stream, when used, will look for all implementations of this interface. The name of your component should describe what you are importing.

Interface OutputInstanceFilterStreamFactory extends interface org.xwiki.filter.output.OutputFilterStreamFactory. This interface defines a createOutputFilterStream method that takes a property map and returns an OutputFilterStream object. OutputFilterStreamFactory extends FilterStreamFactory which defines getType(), getDescriptor() and getFilterInterfaces(). The descriptor gives a title and a description for your filter stream. The filter interfaces defines which events it must receive.

Implementing OutputInstanceFilterStreamFactory is a lot of administrative and ceremonious work. Helpers such as org.xwiki.filter.AbstractFilterStreamFactory and AbstractFilterStreamDescriptor makes this a lot easier. The former takes the type as its constructor parameter, and the latter helps create a minimal descriptor.

Example

@Component
@Singleton
@Named(FavoriteInstanceOutputFilterStreamFactory.ID)
public class FavoriteInstanceOutputFilterStreamFactory extends AbstractFilterStreamFactory implements
    OutputInstanceFilterStreamFactory
{
    static final String ID = "favorite";

    private static final Collection<Class<?>> FILTER_INTERFACES =
        Collections.singletonList(FavoriteInstanceOutputFilter.class);

    private static final FilterStreamDescriptor FILTER_DESCRIPTOR = new AbstractFilterStreamDescriptor(
        "XWiki favorites instance output stream",
        "Specialized version of the XWiki instance output stream for favorites."
    ) { };

    @Inject
    private FavoriteManager favoriteManager;

    /**
     * Constructor.
     */
    public FavoriteInstanceOutputFilterStreamFactory()
    {
        super(new FilterStreamType(FilterStreamType.XWIKI_INSTANCE.getType(),
            FilterStreamType.XWIKI_INSTANCE.getDataFormat() + "+" + ID));

        setDescriptor(FILTER_DESCRIPTOR);
    }

    @Override
    public Collection<Class<?>> getFilterInterfaces()
    {
        return FILTER_INTERFACES;
    }

    @Override
    public OutputFilterStream createOutputFilterStream(Map<String, Object> properties)
    {
        return new FavoriteInstanceOutputFilterStream(favoriteManager);
    }
}
Information

In this example, the output filter stream is not a component and this is quickly annoying if you need to use various features of XWiki. A way to deal with it is to create a interface with a @Role annotation (it can be an interface extending OutputFilterStream - in the example, FavoriteOutputFilterStream). Then, you'd make sure that your component is implemented each time it is requested through the component manager using @InstantiationStrategy(ComponentInstantiationStrategy.PER_LOOKUP). Then, your createOutputFilterStream method would use the component manager to get the component and return it.

Supporting parameters in your filter stream

So far, we have a filter that takes no parameters. The descriptor provides methods to declare which parameters your filter support. The idiomatic way to handle this is to

  • create a class that extends InstanceOutputProperties, and then define setters, and getters with property-related annotations like @PropertyName and @PropertyDescription
  • Make your output filter stream a component extending AbstractBeanOutputFilterStream (you don't need to implement getFilter anymore)
  • Make your factory extend AbstractBeanOutputInstanceFilterStreamFactory

Example

(We assume the properties class is named FavoriteInstanceOutputProperties)

The output filter:

package org.xwiki.contrib.favorites.internal.filter.output;
 
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
 
import javax.inject.Named;

import org.xwiki.component.annotation.Component;
import org.xwiki.component.annotation.InstantiationStrategy;
import org.xwiki.component.descriptor.ComponentInstantiationStrategy;
import org.xwiki.contrib.favorites.FavoritesException;
import org.xwiki.contrib.favorites.FavoriteManager;
import org.xwiki.filter.FilterEventParameters;
import org.xwiki.filter.FilterException;
import org.xwiki.filter.output.AbstractBeanOutputFilterStream;
import org.xwiki.model.EntityType;
import org.xwiki.model.reference.EntityReference;
import org.xwiki.model.reference.WikiReference;

@Component
@Named(FavoriteInstanceOutputFilterStreamFactory.ROLEHINT)
@InstantiationStrategy(ComponentInstantiationStrategy.PER_LOOKUP)
public class FavoriteInstanceOutputFilterStream extends AbstractBeanOutputFilterStream<FavoriteInstanceOutputProperties> implements FavoriteInstanceOutputFilter
{
    private EntityReference currentReference;
 
    @Inject
    private FavoriteManager favoriteManager;
 
    @Override
    public void close() throws IOException
    //... 

The factory, which is way shorter (this is the happy path!):

package org.xwiki.contrib.favorites.internal.filter.output;

import javax.inject.Named;
import javax.inject.Singleton;
 
import org.xwiki.component.annotation.Component;
import org.xwiki.filter.instance.internal.InstanceUtils;
import org.xwiki.filter.instance.internal.output.AbstractBeanOutputInstanceFilterStreamFactory;
 
@Component
@Singleton
@Named(FavoriteInstanceOutputFilterStreamFactory.ID)
public class FavoriteInstanceOutputFilterStreamFactory extends AbstractBeanOutputInstanceFilterStreamFactory<FavoriteInstanceOutputProperties, FavoriteInstanceOutputFilter>
{
    public static final String ID = "favorite";
    public static final String ROLEHINT = InstanceUtils.ROLEHINT + '+' + ID;

    public FavoriteInstanceOutputFilterStreamFactory()
    {
        super(ID);
        setName("XWiki favorites instance output stream");
        setDescription("Specialized version of the XWiki instance output stream for favorites.");
    }
}

Get Connected