Extending the output instance filter
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);
}
}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.");
}
}