org.xwiki.wikistream.instance.internal.output.XWikiDocumentOutputWikiStream.java Source code

Java tutorial

Introduction

Here is the source code for org.xwiki.wikistream.instance.internal.output.XWikiDocumentOutputWikiStream.java

Source

/*
 * 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.wikistream.instance.internal.output;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.util.Date;
import java.util.Locale;
import java.util.regex.Pattern;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.commons.lang3.reflect.TypeUtils;
import org.xwiki.component.annotation.Component;
import org.xwiki.component.annotation.InstantiationStrategy;
import org.xwiki.component.descriptor.ComponentInstantiationStrategy;
import org.xwiki.component.manager.ComponentLookupException;
import org.xwiki.component.manager.ComponentManager;
import org.xwiki.filter.FilterDescriptorManager;
import org.xwiki.filter.FilterEventParameters;
import org.xwiki.model.EntityType;
import org.xwiki.model.reference.DocumentReferenceResolver;
import org.xwiki.model.reference.EntityReference;
import org.xwiki.model.reference.EntityReferenceResolver;
import org.xwiki.properties.ConverterManager;
import org.xwiki.rendering.listener.WrappingListener;
import org.xwiki.rendering.renderer.PrintRendererFactory;
import org.xwiki.rendering.renderer.printer.DefaultWikiPrinter;
import org.xwiki.rendering.syntax.Syntax;
import org.xwiki.wikistream.WikiStreamException;
import org.xwiki.wikistream.filter.xwiki.XWikiWikiAttachmentFilter;
import org.xwiki.wikistream.filter.xwiki.XWikiWikiDocumentFilter;
import org.xwiki.wikistream.instance.internal.XWikiDocumentFilter;
import org.xwiki.wikistream.instance.output.DocumentInstanceOutputProperties;
import org.xwiki.wikistream.model.filter.WikiAttachmentFilter;
import org.xwiki.wikistream.model.filter.WikiClassFilter;
import org.xwiki.wikistream.model.filter.WikiDocumentFilter;
import org.xwiki.wikistream.model.filter.WikiObjectFilter;

import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.XWikiException;
import com.xpn.xwiki.doc.XWikiAttachment;
import com.xpn.xwiki.doc.XWikiDocument;
import com.xpn.xwiki.internal.objects.classes.PropertyClassProvider;
import com.xpn.xwiki.internal.objects.meta.PropertyMetaClassInterface;
import com.xpn.xwiki.objects.BaseObject;
import com.xpn.xwiki.objects.BaseObjectReference;
import com.xpn.xwiki.objects.BaseProperty;
import com.xpn.xwiki.objects.PropertyInterface;
import com.xpn.xwiki.objects.classes.BaseClass;
import com.xpn.xwiki.objects.classes.PropertyClass;
import com.xpn.xwiki.objects.classes.PropertyClassInterface;

/**
 * @version $Id: d150ddbb2347621da8c16dcd11f79fed654f0f6d $
 * @since 5.4M1
 */
@Component(roles = XWikiDocumentOutputWikiStream.class)
@InstantiationStrategy(ComponentInstantiationStrategy.PER_LOOKUP)
public class XWikiDocumentOutputWikiStream implements XWikiDocumentFilter {
    private static final Pattern VALID_VERSION = Pattern.compile("\\d*\\.\\d*");

    @Inject
    private FilterDescriptorManager filterManager;

    @Inject
    @Named("current")
    private DocumentReferenceResolver<EntityReference> entityResolver;

    @Inject
    @Named("relative")
    private EntityReferenceResolver<String> relativeResolver;

    @Inject
    private ConverterManager converter;

    @Inject
    @Named("context")
    private Provider<ComponentManager> componentManagerProvider;

    @Inject
    private Provider<XWikiContext> xcontextProvider;

    private DocumentInstanceOutputProperties properties;

    private WrappingListener contentListener = new WrappingListener();

    private DefaultWikiPrinter currentWikiPrinter;

    private EntityReference currentEntityReference;

    private Locale currentLocale;

    private FilterEventParameters currentLocaleParameters;

    private Locale currentDefaultLocale;

    private XWikiDocument document;

    private BaseClass currentXClass;

    private PropertyClass currentClassProperty;

    private PropertyMetaClassInterface currentClassPropertyMeta;

    private BaseObject currentXObject;

    private BaseClass currentXObjectClass;

    private Object filter;

    protected Object getFilter() throws WikiStreamException {
        if (this.filter == null) {
            this.filter = this.filterManager.createCompositeFilter(this.contentListener, this);
        }

        return this.filter;
    }

    public void setProperties(DocumentInstanceOutputProperties properties) {
        this.properties = properties;
    }

    public XWikiDocument getDocument() {
        return this.document;
    }

    private <T> T get(Type type, String key, FilterEventParameters parameters, T def) {
        return get(type, key, parameters, def, true);
    }

    private <T> T get(Type type, String key, FilterEventParameters parameters, T def, boolean replaceNull) {
        if (!parameters.containsKey(key)) {
            return def;
        }

        Object value = parameters.get(key);

        if (value == null) {
            return replaceNull ? def : null;
        }

        if (TypeUtils.isInstance(value, type)) {
            return (T) value;
        }

        return this.converter.convert(type, value);
    }

    private Date getDate(String key, FilterEventParameters parameters, Date def) {
        return get(Date.class, key, parameters, def);
    }

    private String getString(String key, FilterEventParameters parameters, String def) {
        return get(String.class, key, parameters, def);
    }

    private boolean getBoolean(String key, FilterEventParameters parameters, boolean def) {
        return get(boolean.class, key, parameters, def);
    }

    private int getInt(String key, FilterEventParameters parameters, int def) {
        return get(int.class, key, parameters, def);
    }

    private Syntax getSyntax(String key, FilterEventParameters parameters, Syntax def) {
        return get(Syntax.class, key, parameters, def);
    }

    private EntityReference getEntityReference(String key, FilterEventParameters parameters, EntityReference def) {
        Object reference = get(Object.class, key, parameters, def);

        if (reference instanceof EntityReference) {
            return (EntityReference) reference;
        }

        return reference != null
                ? this.relativeResolver.resolve(reference.toString(), EntityType.DOCUMENT, parameters)
                : def;
    }

    // Events

    @Override
    public void beginWiki(String name, FilterEventParameters parameters) throws WikiStreamException {
        this.currentEntityReference = new EntityReference(name, EntityType.WIKI, this.currentEntityReference);
    }

    @Override
    public void endWiki(String name, FilterEventParameters parameters) throws WikiStreamException {
        this.currentEntityReference = this.currentEntityReference.getParent();
    }

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

    @Override
    public void endWikiSpace(String name, FilterEventParameters parameters) throws WikiStreamException {
        this.currentEntityReference = this.currentEntityReference.getParent();
    }

    @Override
    public void beginWikiDocument(String name, FilterEventParameters parameters) throws WikiStreamException {
        this.currentEntityReference = new EntityReference(name, EntityType.DOCUMENT, this.currentEntityReference);

        this.currentDefaultLocale = (Locale) parameters.get(WikiDocumentFilter.PARAMETER_LOCALE);
    }

    @Override
    public void endWikiDocument(String name, FilterEventParameters parameters) throws WikiStreamException {
        this.currentEntityReference = this.currentEntityReference.getParent();

        this.currentDefaultLocale = null;
    }

    @Override
    public void beginWikiDocumentLocale(Locale locale, FilterEventParameters parameters)
            throws WikiStreamException {
        this.currentLocale = locale;
        this.currentLocaleParameters = parameters;
    }

    @Override
    public void endWikiDocumentLocale(Locale locale, FilterEventParameters parameters) throws WikiStreamException {
        this.currentLocale = null;
        this.currentLocaleParameters = null;
    }

    @Override
    public void beginWikiDocumentRevision(String version, FilterEventParameters parameters)
            throws WikiStreamException {
        this.document = new XWikiDocument(this.entityResolver.resolve(this.currentEntityReference,
                this.properties != null ? this.properties.getDefaultReference() : null));

        this.document.setCreationDate(
                getDate(WikiDocumentFilter.PARAMETER_CREATION_DATE, this.currentLocaleParameters, null));
        if (this.currentLocaleParameters.containsKey(WikiDocumentFilter.PARAMETER_CREATION_AUTHOR)) {
            this.document.setCreator(
                    getString(WikiDocumentFilter.PARAMETER_CREATION_AUTHOR, this.currentLocaleParameters, null));
        }
        this.document.setDefaultLocale(this.currentDefaultLocale);

        this.document.setSyntax(getSyntax(WikiDocumentFilter.PARAMETER_SYNTAX, parameters, null));

        this.document.setLocale(this.currentLocale);

        this.document.setParentReference(getEntityReference(WikiDocumentFilter.PARAMETER_PARENT, parameters, null));
        this.document.setCustomClass(getString(WikiDocumentFilter.PARAMETER_CUSTOMCLASS, parameters, null));
        this.document.setTitle(getString(WikiDocumentFilter.PARAMETER_TITLE, parameters, null));
        this.document.setDefaultTemplate(getString(WikiDocumentFilter.PARAMETER_DEFAULTTEMPLATE, parameters, null));
        this.document
                .setValidationScript(getString(WikiDocumentFilter.PARAMETER_VALIDATIONSCRIPT, parameters, null));
        this.document.setHidden(getBoolean(WikiDocumentFilter.PARAMETER_HIDDEN, parameters, false));

        this.document.setMinorEdit(getBoolean(WikiDocumentFilter.PARAMETER_REVISION_MINOR, parameters, false));
        if (parameters.containsKey(WikiDocumentFilter.PARAMETER_REVISION_AUTHOR)) {
            this.document.setAuthor(getString(WikiDocumentFilter.PARAMETER_REVISION_AUTHOR, parameters, null));
        }
        this.document.setContentAuthor(getString(WikiDocumentFilter.PARAMETER_CONTENT_AUTHOR, parameters, null));

        String revisions = getString(XWikiWikiDocumentFilter.PARAMETER_JRCSREVISIONS, this.currentLocaleParameters,
                null);
        if (revisions != null) {
            try {
                this.document.setDocumentArchive(revisions);
            } catch (XWikiException e) {
                throw new WikiStreamException("Failed to set document archive", e);
            }
        }

        if (version != null && this.properties.isVersionPreserved()) {
            if (VALID_VERSION.matcher(version).matches()) {
                this.document.setVersion(version);
            } else if (NumberUtils.isDigits(version)) {
                this.document.setVersion(version + ".1");
            } else {
                // TODO: log something, probably a warning
            }
        }

        this.document.setDate(getDate(WikiDocumentFilter.PARAMETER_REVISION_DATE, parameters, new Date()));
        this.document.setComment(getString(WikiDocumentFilter.PARAMETER_REVISION_COMMENT, parameters, ""));

        this.document
                .setContentUpdateDate(getDate(WikiDocumentFilter.PARAMETER_CONTENT_DATE, parameters, new Date()));

        // Content

        if (parameters.containsKey(WikiDocumentFilter.PARAMETER_CONTENT)) {
            this.document.setContent(getString(WikiDocumentFilter.PARAMETER_CONTENT, parameters, null));
        } else {
            if (this.properties != null && this.properties.getDefaultSyntax() != null) {
                this.document.setSyntax(this.properties.getDefaultSyntax());
            } else {
                // Make sure to set the default syntax if none were provided
                this.document.setSyntax(this.document.getSyntax());
            }

            ComponentManager componentManager = this.componentManagerProvider.get();

            if (componentManager.hasComponent(PrintRendererFactory.class, this.document.getSyntax().toIdString())) {
                PrintRendererFactory rendererFactory;
                try {
                    rendererFactory = componentManager.getInstance(PrintRendererFactory.class,
                            this.document.getSyntax().toIdString());
                } catch (ComponentLookupException e) {
                    throw new WikiStreamException(String.format(
                            "Failed to find PrintRendererFactory for syntax [%s]", this.document.getSyntax()), e);
                }

                this.currentWikiPrinter = new DefaultWikiPrinter();
                this.contentListener.setWrappedListener(rendererFactory.createRenderer(this.currentWikiPrinter));
            }
        }
    }

    @Override
    public void endWikiDocumentRevision(String version, FilterEventParameters parameters)
            throws WikiStreamException {
        // Set content
        if (this.currentWikiPrinter != null) {
            this.document.setContent(this.currentWikiPrinter.getBuffer().toString());

            this.contentListener.setWrappedListener(null);
            this.currentWikiPrinter = null;
        }

    }

    @Override
    public void onWikiAttachment(String name, InputStream content, Long size, FilterEventParameters parameters)
            throws WikiStreamException {
        XWikiAttachment attachment = new XWikiAttachment(this.document, name);

        try {
            attachment.setContent(content);
        } catch (IOException e) {
            throw new WikiStreamException("Failed to set attachment content", e);
        }

        // Author

        attachment.setAuthor(getString(WikiAttachmentFilter.PARAMETER_REVISION_AUTHOR, parameters, ""));

        // Version

        if (this.properties == null || this.properties.isVersionPreserved()) {
            if (parameters.containsKey(WikiAttachmentFilter.PARAMETER_REVISION)) {
                String version = getString(WikiAttachmentFilter.PARAMETER_REVISION, parameters, null);
                if (version != null) {
                    if (VALID_VERSION.matcher(version).matches()) {
                        attachment.setVersion(version);
                    } else if (NumberUtils.isDigits(version)) {
                        attachment.setVersion(version + ".1");
                    } else {
                        // TODO: log something, probably a warning
                    }
                }
            }
            attachment.setComment(getString(WikiAttachmentFilter.PARAMETER_REVISION_COMMENT, parameters, ""));
            attachment.setDate(getDate(WikiAttachmentFilter.PARAMETER_REVISION_DATE, parameters, new Date()));

            String revisions = getString(XWikiWikiAttachmentFilter.PARAMETER_JRCSREVISIONS, parameters, null);
            if (revisions != null) {
                try {
                    attachment.setArchive(revisions);
                } catch (XWikiException e) {
                    throw new WikiStreamException("Failed to set attachment archive", e);
                }
            }

            attachment.setMetaDataDirty(false);
        }

        this.document.addAttachment(attachment);
    }

    @Override
    public void beginWikiClass(FilterEventParameters parameters) throws WikiStreamException {
        if (this.currentXObject != null) {
            this.currentXClass = new BaseClass();
            this.currentXClass.setDocumentReference(this.currentXObject.getXClassReference());
            this.currentXObjectClass = this.currentXClass;
        } else {
            this.currentXClass = this.document.getXClass();
        }

        this.currentXClass.setCustomClass(getString(WikiClassFilter.PARAMETER_CUSTOMCLASS, parameters, null));
        this.currentXClass.setCustomMapping(getString(WikiClassFilter.PARAMETER_CUSTOMMAPPING, parameters, null));
        this.currentXClass
                .setDefaultViewSheet(getString(WikiClassFilter.PARAMETER_SHEET_DEFAULTVIEW, parameters, null));
        this.currentXClass
                .setDefaultEditSheet(getString(WikiClassFilter.PARAMETER_SHEET_DEFAULTEDIT, parameters, null));
        this.currentXClass.setDefaultWeb(getString(WikiClassFilter.PARAMETER_DEFAULTSPACE, parameters, null));
        this.currentXClass.setNameField(getString(WikiClassFilter.PARAMETER_NAMEFIELD, parameters, null));
        this.currentXClass
                .setValidationScript(getString(WikiClassFilter.PARAMETER_VALIDATIONSCRIPT, parameters, null));
    }

    @Override
    public void endWikiClass(FilterEventParameters parameters) throws WikiStreamException {
        this.currentXClass = null;
    }

    @Override
    public void beginWikiClassProperty(String name, String type, FilterEventParameters parameters)
            throws WikiStreamException {
        ComponentManager componentManager = this.componentManagerProvider.get();

        PropertyClassProvider provider;

        // First try to use the specified class type as hint.
        try {
            if (componentManager.hasComponent(PropertyClassProvider.class, type)) {
                provider = componentManager.getInstance(PropertyClassProvider.class, type);
            } else {
                // In previous versions the class type was the full Java class name of the property class
                // implementation. Extract the hint by removing the Java package prefix and the Class suffix.
                String classType = StringUtils.removeEnd(StringUtils.substringAfterLast(type, "."), "Class");
                provider = componentManager.getInstance(PropertyClassProvider.class, classType);
            }
        } catch (ComponentLookupException e) {
            throw new WikiStreamException(
                    String.format("Failed to get instance of the property class provider for type [%s]", type), e);
        }

        this.currentClassPropertyMeta = provider.getDefinition();

        // We should use PropertyClassInterface (instead of PropertyClass, its default implementation) but it
        // doesn't have the set methods and adding them would breaks the backwards compatibility. We make the
        // assumption that all property classes extend PropertyClass.
        this.currentClassProperty = (PropertyClass) provider.getInstance();
        this.currentClassProperty.setName(name);
        this.currentClassProperty.setObject(this.currentXClass);

        this.currentXClass.safeput(name, this.currentClassProperty);
    }

    @Override
    public void endWikiClassProperty(String name, String type, FilterEventParameters parameters)
            throws WikiStreamException {
        this.currentClassPropertyMeta = null;
        this.currentClassProperty = null;
    }

    @Override
    public void onWikiClassPropertyField(String name, String value, FilterEventParameters parameters)
            throws WikiStreamException {
        PropertyClass propertyClass;
        try {
            propertyClass = (PropertyClass) this.currentClassPropertyMeta.get(name);
        } catch (XWikiException e) {
            throw new WikiStreamException(
                    String.format("Failed to get definition of field [%s] for property type [%s]", name,
                            this.currentClassProperty.getClassType()),
                    e);
        }

        BaseProperty<?> field = propertyClass.fromString(value);

        this.currentClassProperty.safeput(name, field);
    }

    @Override
    public void beginWikiObject(String name, FilterEventParameters parameters) throws WikiStreamException {
        if (name != null) {
            this.currentEntityReference = new EntityReference(name, EntityType.OBJECT, this.currentEntityReference);
        }

        this.currentXObject = new BaseObject();

        int number = getInt(WikiObjectFilter.PARAMETER_NUMBER, parameters, -1);

        String className = getString(WikiObjectFilter.PARAMETER_CLASS_REFERENCE, parameters, null);
        if (className == null) {
            BaseObjectReference reference = new BaseObjectReference(this.currentEntityReference);

            this.currentXObject.setXClassReference(reference.getXClassReference());

            if (number < 0 && reference.getObjectNumber() != null) {
                number = reference.getObjectNumber();
            }
        } else {
            this.currentXObject.setClassName(className);
        }

        if (number < 0) {
            this.document.addXObject(this.currentXObject);
        } else {
            this.document.setXObject(number, this.currentXObject);
        }

        this.currentXObject.setGuid(getString(WikiObjectFilter.PARAMETER_GUID, parameters, null));
    }

    @Override
    public void endWikiObject(String name, FilterEventParameters parameters) throws WikiStreamException {
        if (this.currentEntityReference.getType() == EntityType.OBJECT) {
            this.currentEntityReference = this.currentEntityReference.getParent();
        }

        this.currentXObject = null;
        this.currentXObjectClass = null;
    }

    private BaseClass getCurrentXClass() throws WikiStreamException {
        if (this.currentXObjectClass == null) {
            XWikiContext xcontext = this.xcontextProvider.get();
            if (xcontext != null) {
                try {
                    return xcontext.getWiki().getXClass(this.currentXObject.getXClassReference(), xcontext);
                } catch (XWikiException e) {
                    throw new WikiStreamException("Unexpected error when trying to get class ["
                            + this.currentXObject.getXClassReference() + "]", e);
                }
            }
        }

        return this.currentXObjectClass;
    }

    @Override
    public void onWikiObjectProperty(String name, Object value, FilterEventParameters parameters)
            throws WikiStreamException {
        PropertyClassInterface propertyclass = (PropertyClassInterface) getCurrentXClass().safeget(name);

        if (propertyclass != null) {
            // Bulletproofing using PropertyClassInterface#fromString when a String is passed (in case it's not really a
            // String property)
            PropertyInterface property = value instanceof String ? propertyclass.fromString((String) value)
                    : propertyclass.fromValue(value);

            this.currentXObject.safeput(name, property);
        } else {
            // TODO: Log something ?
        }
    }
}