Commit 2a4c21e1 authored by tmortagne's avatar tmortagne
Browse files

XCOMMONS-433: Provide a generic XML parser/serializer for filters

parent 87a07bcb
......@@ -37,6 +37,7 @@
<modules>
<module>xwiki-commons-filter-api</module>
<module>xwiki-commons-filter-xml</module>
<module>xwiki-commons-filter-json</module>
</modules>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<!--
*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.xwiki.commons</groupId>
<artifactId>xwiki-commons-filter</artifactId>
<version>5.2-SNAPSHOT</version>
</parent>
<artifactId>xwiki-commons-filter-json</artifactId>
<name>XWiki Commons - Filter - JSON</name>
<packaging>jar</packaging>
<description>XWiki Commons - Filter - JSON</description>
<properties>
<xwiki.jacoco.instructionRatio>0</xwiki.jacoco.instructionRatio>
</properties>
<dependencies>
<dependency>
<groupId>org.xwiki.commons</groupId>
<artifactId>xwiki-commons-component-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.xwiki.commons</groupId>
<artifactId>xwiki-commons-filter-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.2.2</version>
</dependency>
<!-- Test dependencies -->
<dependency>
<groupId>org.xwiki.commons</groupId>
<artifactId>xwiki-commons-test-component</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.xwiki.commons</groupId>
<artifactId>xwiki-commons-filter-api</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<configuration>
<excludes>
**/*.java
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<!--
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
-->
<!DOCTYPE suppressions PUBLIC
"-//Puppy Crawl//DTD Suppressions 1.0//EN"
"http://www.puppycrawl.com/dtds/suppressions_1_0.dtd">
<suppressions>
<!-- This method is supposed to return HashSet, that the type it's handling... -->
<suppress checks="IllegalType" files="HashSetConverter.java" />
</suppressions>
/*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.xwiki.filter.json;
import java.util.regex.Pattern;
/**
* Allow to customize the syntax.
*
* @version $Id$
* @since 5.2M1
*/
public class JSONConfiguration
{
/**
* @see #getBlockChildrenField()
*/
public static final String DEFAULT_BLOCK_CHILDREN_NAME = "c";
/**
* @see #getBlockTypeField()
*/
public static final String DEFAULT_BLOCK_TYPE_NAME = "t";
/**
* @see #getParameterField()
*/
public static final String DEFAULT_BLOCK_PARAMETER_NAME = "p";
/**
* @see #getBlockChildrenField()
*/
private String blockChildrenField;
/**
* @see #getBlockTypeField()
*/
private String blockTypeField;
/**
* @see #getBlockParameterField()
*/
private String blockParameterField;
/**
* @see #getElementParameterPattern()
*/
private Pattern blockParameterFieldPattern;
/**
* Default constructor.
*/
public JSONConfiguration()
{
setChildrenField(DEFAULT_BLOCK_CHILDREN_NAME);
setBlockTypeField(DEFAULT_BLOCK_TYPE_NAME);
}
/**
* @return the default name of children field.
*/
public String getBlockChildrenField()
{
return this.blockChildrenField;
}
/**
* @param childrenField the default name of children field.
*/
public void setChildrenField(String childrenField)
{
this.blockChildrenField = childrenField;
}
/**
* @return the default name of block type field.
*/
public String getBlockTypeField()
{
return this.blockTypeField;
}
/**
* @param blockTypeField the default name of block type field.
*/
public void setBlockTypeField(String blockTypeField)
{
this.blockTypeField = blockTypeField;
}
/**
* @return the generic name prefix of a field containing a parameter
*/
public String getBlockParameterField()
{
return this.blockParameterField;
}
/**
* @param blockParameterField the generic name prefix of a field containing a parameter
*/
public void setBlockParameterField(String blockParameterField)
{
this.blockParameterField = blockParameterField;
this.blockParameterFieldPattern = Pattern.compile(Pattern.quote(blockParameterField) + "(\\d*)");
}
/**
* @return the pattern marching the name of a field containing a parameter
*/
public Pattern getBlockParameterFieldPattern()
{
return this.blockParameterFieldPattern;
}
}
/*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.xwiki.filter.json.internal.serializer;
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Stack;
import javax.inject.Singleton;
import org.xwiki.component.annotation.Component;
import org.xwiki.filter.FilterDescriptor;
import org.xwiki.filter.FilterElement;
import org.xwiki.filter.FilterElementParameter;
import org.xwiki.filter.json.JSONConfiguration;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* Proxy called as an event filter to write JSON elements.
*
* @version $Id$
* @since 5.2M1
*/
@Component
@Singleton
public class DefaultJSONSerializer implements InvocationHandler
{
private final FilterDescriptor descriptor;
private final JSONConfiguration configuration;
private Stack<Integer> index = new Stack<Integer>();
private JsonGenerator generator;
public DefaultJSONSerializer(Writer writer, FilterDescriptor descriptor, JSONConfiguration configuration)
throws IOException
{
this.descriptor = descriptor;
this.configuration = configuration != null ? configuration : new JSONConfiguration();
JsonFactory jsonFactory = new JsonFactory();
this.generator = jsonFactory.createGenerator(writer);
this.generator.setCodec(new ObjectMapper());
this.generator.useDefaultPrettyPrinter();
}
private String getBlockType(String eventName, String prefix)
{
String blockName = eventName.substring(prefix.length());
blockName = Character.toLowerCase(blockName.charAt(0)) + blockName.substring(1);
return blockName;
}
private void incrementIndex()
{
this.index.set(this.index.size() - 1, this.index.peek() + 1);
}
private void pushIndex()
{
this.index.push(0);
}
private void popIndex()
{
this.index.pop();
}
private int getIndex()
{
return this.index.size() > 0 ? this.index.peek() : 0;
}
private void beginEvent(String eventName, Object[] parameters) throws IOException
{
if (this.index.size() > 0 && getIndex() == 0) {
this.generator.writeFieldName(this.configuration.getBlockChildrenField());
}
String blockType = getBlockType(eventName, "begin");
FilterElement element = this.descriptor.getElements().get(blockType.toLowerCase());
List<Object> elementParameters = parameters != null ? Arrays.asList(parameters) : null;
// Print start element
this.generator.writeStartObject();
this.generator.writeStringField(this.configuration.getBlockTypeField(), blockType);
// Write complex parameters
writeParameters(elementParameters, element);
pushIndex();
}
private void endEvent() throws IOException
{
if (this.index.size() > 0 && getIndex() > 1) {
this.generator.writeEndArray();
}
popIndex();
this.generator.writeEndObject();
if (!this.index.isEmpty()) {
incrementIndex();
}
this.generator.flush();
}
private void onEvent(String eventName, Object[] parameters) throws IOException
{
if (this.index.size() > 0 && getIndex() == 0) {
this.generator.writeArrayFieldStart(this.configuration.getBlockChildrenField());
}
String blockType = getBlockType(eventName, "on");
FilterElement element = this.descriptor.getElements().get(blockType.toLowerCase());
List<Object> elementParameters = parameters != null ? Arrays.asList(parameters) : null;
this.generator.writeStartObject();
this.generator.writeStringField(this.configuration.getBlockTypeField(), blockType);
writeParameters(elementParameters, element);
pushIndex();
endEvent();
}
private void writeParameters(List<Object> parameters, FilterElement descriptor) throws IOException
{
if (parameters != null && !parameters.isEmpty()) {
for (int i = 0; i < parameters.size(); ++i) {
Object value = parameters.get(i);
if (value != null) {
FilterElementParameter filterParameter = descriptor.getParameters()[i];
String elementName;
if (filterParameter.getName() != null) {
elementName = filterParameter.getName();
} else {
elementName = this.configuration.getBlockParameterField() + filterParameter.getIndex();
}
this.generator.writeObjectField(elementName, parameters.get(i));
}
}
}
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
Object result = null;
if (method.getName().startsWith("begin")) {
beginEvent(method.getName(), args);
} else if (method.getName().startsWith("end")) {
endEvent();
} else if (method.getName().startsWith("on")) {
onEvent(method.getName(), args);
} else {
throw new NoSuchMethodException(method.toGenericString());
}
return result;
}
}
/*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.xwiki.filter.json.internal.serializer;
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Proxy;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.xwiki.component.annotation.Component;
import org.xwiki.filter.FilterDescriptorManager;
import org.xwiki.filter.json.JSONConfiguration;
import org.xwiki.filter.json.serializer.JSONSerializerFactory;
/**
* Default implementation of {@link JSONSerializerFactory}.
*
* @version $Id$
* @since 5.2M1
*/
@Component
@Singleton
public class DefaultJSONSerializerFactory implements JSONSerializerFactory
{
/**
* The events supported by the filter.
*/
@Inject
private FilterDescriptorManager descriptorManager;
@Override
public <T> T createSerializer(Class<T> filterInterface, Writer writer, JSONConfiguration configuration) throws IOException
{
DefaultJSONSerializer handler =
new DefaultJSONSerializer(writer,
this.descriptorManager.getFilterDescriptor(filterInterface), configuration);
return (T) Proxy.newProxyInstance(filterInterface.getClassLoader(), new Class[] {filterInterface}, handler);
}
}
/*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.xwiki.filter.json.parser;
import java.io.Reader;
import org.xwiki.component.annotation.Role;
import org.xwiki.filter.json.JSONConfiguration;
/**