com.xpn.xwiki.objects.BaseElement.java Source code

Java tutorial

Introduction

Here is the source code for com.xpn.xwiki.objects.BaseElement.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 com.xpn.xwiki.objects;

import java.io.IOException;
import java.io.Serializable;
import java.io.StringWriter;

import javax.inject.Provider;

import org.apache.commons.lang3.StringUtils;
import org.dom4j.Element;
import org.dom4j.io.DocumentResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xwiki.filter.input.StringInputSource;
import org.xwiki.filter.xar.output.XAROutputProperties;
import org.xwiki.filter.xml.output.DefaultResultOutputTarget;
import org.xwiki.localization.ContextualLocalizationManager;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.model.reference.EntityReference;
import org.xwiki.model.reference.EntityReferenceSerializer;

import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.XWikiException;
import com.xpn.xwiki.doc.XWikiDocument;
import com.xpn.xwiki.doc.merge.MergeConfiguration;
import com.xpn.xwiki.doc.merge.MergeResult;
import com.xpn.xwiki.internal.filter.XWikiDocumentFilterUtils;
import com.xpn.xwiki.internal.merge.MergeUtils;
import com.xpn.xwiki.util.Util;
import com.xpn.xwiki.web.Utils;

/**
 * Base class for representing an element having a name (either a reference of a free form name) and a pretty name.
 *
 * @version $Id: c77bd51f37733c4611d22198473a4558768cb30b $
 */
public abstract class BaseElement<R extends EntityReference> implements ElementInterface, Serializable {
    protected static final Logger LOGGER = LoggerFactory.getLogger(BaseObject.class);

    /**
     * Full reference of this element.
     *
     * @since 3.2M1
     */
    protected R referenceCache;

    /**
     * Reference to the document in which this element is defined (for elements where this make sense, for example for
     * an XClass or a XObject).
     *
     * @since 5.3M1
     */
    protected DocumentReference documentReference;

    /**
     * The owner document, if this element was obtained from a document.
     *
     * @since 5.3M1
     */
    protected transient XWikiDocument ownerDocument;

    /**
     * Free form name (for elements which don't point to a reference, for example for instances of {@link BaseProperty}
     * ).
     */
    private String name;

    private String prettyName;

    /**
     * Used to convert a proper Document Reference to a string but without the wiki name.
     */
    private EntityReferenceSerializer<String> localEntityReferenceSerializer;

    /**
     * Used to build uid string for the getId() hash.
     */
    private EntityReferenceSerializer<String> localUidStringEntityReferenceSerializer;

    private ContextualLocalizationManager localization;

    @Override
    public R getReference() {
        if (this.referenceCache == null) {
            this.referenceCache = createReference();
        }

        return this.referenceCache;
    }

    /**
     * @since 3.2M1
     */
    protected R createReference() {
        return null;
    }

    @Override
    public DocumentReference getDocumentReference() {
        // Object using name without setting a reference are not allowed to retrieve the reference
        if (this.documentReference == null && this.name != null) {
            throw new IllegalStateException(
                    "BaseElement#getDocumentReference could not be called when a non-reference Name has been set.");
        }

        return this.documentReference;
    }

    /**
     * {@inheritDoc}
     * <p>
     * Note that this method is used by Hibernate for saving an element.
     * </p>
     *
     * @see com.xpn.xwiki.objects.ElementInterface#getName()
     */
    @Override
    public String getName() {
        // If the name is null then serialize the reference as a string.
        if (this.name == null && this.documentReference != null) {
            this.name = getLocalEntityReferenceSerializer().serialize(this.documentReference);
        }

        return this.name;
    }

    @Override
    public void setDocumentReference(DocumentReference reference) {
        // If the name is already set then reset it since we're now using a reference
        this.documentReference = reference;
        this.name = null;
        this.referenceCache = null;
    }

    /**
     * {@inheritDoc}
     * <p>
     * Note that this method is used by Hibernate for loading an element.
     * </p>
     *
     * @see com.xpn.xwiki.objects.ElementInterface#setName(java.lang.String)
     */
    @Override
    public void setName(String name) {
        // If a reference is already set, then you cannot set a name
        if (this.documentReference != null) {
            throw new IllegalStateException(
                    "BaseElement#setName could not be called when a reference has been set.");
        }

        this.name = name;
        this.referenceCache = null;
    }

    public String getPrettyName() {
        return this.prettyName;
    }

    public void setPrettyName(String name) {
        this.prettyName = name;
    }

    /**
     * @return the component used to build uid string for the getId() hash
     * @since 4.0M1
     */
    protected EntityReferenceSerializer<String> getLocalUidStringEntityReferenceSerializer() {
        if (this.localUidStringEntityReferenceSerializer == null) {
            this.localUidStringEntityReferenceSerializer = Utils.getComponent(EntityReferenceSerializer.TYPE_STRING,
                    "local/uid");
        }

        return this.localUidStringEntityReferenceSerializer;
    }

    /**
     * @return the component used to convert a proper Document Reference to a string but without the wiki name.
     * @since 6.3M1
     */
    protected EntityReferenceSerializer<String> getLocalEntityReferenceSerializer() {
        if (this.localEntityReferenceSerializer == null) {
            this.localEntityReferenceSerializer = Utils.getComponent(EntityReferenceSerializer.TYPE_STRING,
                    "local");
        }

        return this.localEntityReferenceSerializer;
    }

    protected ContextualLocalizationManager getLocalization() {
        if (this.localization == null) {
            this.localization = Utils.getComponent(ContextualLocalizationManager.class);
        }

        return this.localization;
    }

    protected String localizePlain(String key, Object... parameters) {
        return getLocalization().getTranslationPlain(key, parameters);
    }

    protected String localizePlainOrKey(String key, Object... parameters) {
        return StringUtils.defaultString(getLocalization().getTranslationPlain(key, parameters), key);
    }

    /**
     * @return a unique identifier representing this element reference to be used for {@code hashCode()}.
     * @since 4.0M1
     */
    protected String getLocalKey() {
        // The R40000XWIKI6990DataMigration use the same algorithm to compute object id. It should be properly synced.
        return getLocalUidStringEntityReferenceSerializer().serialize(getReference());
    }

    /**
     * Return an truncated MD5 hash of the local key computed in {@link #getLocalKey()}.
     *
     * @return the identifier used by hibernate for storage.
     * @since 4.0M1
     */
    public long getId() {
        String key = getLocalKey();

        if (key != null) {
            // The R40000XWIKI6990DataMigration use the same algorithm to compute object id. It should be properly
            // synced.
            return Util.getHash(key);
        }

        return 0;
    }

    /**
     * Dummy function, do hibernate is always happy.
     *
     * @param id the identifier assigned by hibernate.
     * @since 4.0M1
     */
    public void setId(long id) {
    }

    @Override
    public int hashCode() {
        return (int) Util.getHash(getLocalKey());
    }

    @Override
    public boolean equals(Object el) {
        // Same Java object, they sure are equal
        if (this == el) {
            return true;
        }

        if (el == null || !(el.getClass().equals(this.getClass()))) {
            return false;
        }

        BaseElement element = (BaseElement) el;

        if (element.documentReference != null) {
            if (!element.documentReference.equals(this.documentReference)) {
                return false;
            }
        } else {
            if (this.documentReference != null) {
                return false;
            }
            if (element.name == null) {
                if (this.name != null) {
                    return false;
                }
            } else if (!element.name.equals(this.name)) {
                return false;
            }
        }

        if (element.getPrettyName() == null) {
            if (getPrettyName() != null) {
                return false;
            }
        } else if (!element.getPrettyName().equals(getPrettyName())) {
            return false;
        }

        return true;
    }

    @Override
    public BaseElement clone() {
        BaseElement element;
        try {
            element = (BaseElement) super.clone();

            element.setOwnerDocument(getOwnerDocument());

            // Make sure we clone either the reference or the name depending on which one is used.
            if (this.documentReference != null) {
                element.setDocumentReference(getDocumentReference());
            } else if (this.name != null) {
                element.setName(getName());
            }

            element.setPrettyName(getPrettyName());
        } catch (Exception e) {
            // This should not happen
            element = null;
        }

        return element;
    }

    @Override
    public void merge(ElementInterface previousElement, ElementInterface newElement,
            MergeConfiguration configuration, XWikiContext context, MergeResult mergeResult) {
        setPrettyName(MergeUtils.mergeOject(((BaseElement) previousElement).getPrettyName(),
                ((BaseElement) newElement).getPrettyName(), getPrettyName(), mergeResult));
    }

    @Override
    public boolean apply(ElementInterface newElement, boolean clean) {
        boolean modified = false;

        BaseElement<R> newBaseElement = (BaseElement<R>) newElement;

        // Pretty name
        if (!StringUtils.equals(newBaseElement.getPrettyName(), getPrettyName())) {
            setPrettyName(newBaseElement.getPrettyName());
            modified = true;
        }

        return modified;
    }

    /**
     * Set the owner document of this element.
     *
     * @param ownerDocument The owner document.
     * @since 5.3M1
     */
    public void setOwnerDocument(XWikiDocument ownerDocument) {
        this.ownerDocument = ownerDocument;
    }

    /**
     * @return the owner document of this element.
     * @since 5.3M1
     */
    public XWikiDocument getOwnerDocument() {
        return this.ownerDocument;
    }

    /**
     * Get XWiki context from execution context.
     *
     * @return the XWiki context for the current thread
     * @since 9.0RC1
     */
    protected XWikiContext getXWikiContext() {
        Provider<XWikiContext> xcontextProvider = Utils.getComponent(XWikiContext.TYPE_PROVIDER);

        if (xcontextProvider != null) {
            return xcontextProvider.get();
        }

        return null;
    }

    protected void fromXML(Element oel) throws XWikiException {
        // Serialize the Document (could not find a way to convert a dom4j Element into a usable StAX source)
        StringWriter writer = new StringWriter();
        try {
            org.dom4j.io.XMLWriter domWriter = new org.dom4j.io.XMLWriter(writer);
            domWriter.write(oel);
            domWriter.flush();
        } catch (IOException e) {
            throw new XWikiException(XWikiException.MODULE_XWIKI_DOC, XWikiException.ERROR_DOC_XML_PARSING,
                    "Error parsing xml", e, null);
        }

        // Actually parse the XML
        fromXML(writer.toString());
    }

    /**
     * @param source the XML to read
     * @throws XWikiException when failing to parse XML
     */
    public void fromXML(String source) throws XWikiException {
        if (!source.isEmpty()) {
            try {
                Utils.getComponent(XWikiDocumentFilterUtils.class).importEntity(this,
                        new StringInputSource(source));
            } catch (Exception e) {
                throw new XWikiException(XWikiException.MODULE_XWIKI_DOC, XWikiException.ERROR_DOC_XML_PARSING,
                        "Error parsing xml", e, null);
            }
        }
    }

    protected Element toXML() {
        DocumentResult domResult = new DocumentResult();

        try {
            Utils.getComponent(XWikiDocumentFilterUtils.class).exportEntity(this,
                    new DefaultResultOutputTarget(domResult));
        } catch (Exception e) {
            LOGGER.error("Failed to serialize element to XML", e);

            return null;
        }

        return domResult.getDocument().getRootElement();
    }

    /**
     * @param format true if the XML should be formated
     * @return the XML as a String
     * @since 9.0RC1
     */
    public String toXMLString(boolean format) {
        XAROutputProperties xarProperties = new XAROutputProperties();
        xarProperties.setFormat(false);

        try {
            return Utils.getComponent(XWikiDocumentFilterUtils.class).exportEntity(this, xarProperties);
        } catch (Exception e) {
            LOGGER.error("Failed to serialize collection to XML", e);

            return "";
        }
    }

    @Override
    public String toString() {
        return toXMLString(true);
    }
}