/*
* 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.doc;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringWriter;
import java.lang.ref.SoftReference;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import javax.inject.Provider;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.velocity.VelocityContext;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.dom.DOMDocument;
import org.dom4j.io.DocumentResult;
import org.dom4j.io.OutputFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.suigeneris.jrcs.diff.Diff;
import org.suigeneris.jrcs.diff.DifferentiationFailedException;
import org.suigeneris.jrcs.diff.Revision;
import org.suigeneris.jrcs.diff.delta.Delta;
import org.suigeneris.jrcs.rcs.Version;
import org.suigeneris.jrcs.util.ToString;
import org.xwiki.bridge.DocumentModelBridge;
import org.xwiki.component.util.DefaultParameterizedType;
import org.xwiki.context.Execution;
import org.xwiki.context.ExecutionContextException;
import org.xwiki.context.ExecutionContextManager;
import org.xwiki.display.internal.DocumentDisplayer;
import org.xwiki.display.internal.DocumentDisplayerParameters;
import org.xwiki.filter.input.DefaultInputStreamInputSource;
import org.xwiki.filter.input.InputSource;
import org.xwiki.filter.input.StringInputSource;
import org.xwiki.filter.instance.input.DocumentInstanceInputProperties;
import org.xwiki.filter.instance.output.DocumentInstanceOutputProperties;
import org.xwiki.filter.output.DefaultOutputStreamOutputTarget;
import org.xwiki.filter.output.DefaultWriterOutputTarget;
import org.xwiki.filter.output.OutputTarget;
import org.xwiki.filter.xar.input.XARInputProperties;
import org.xwiki.filter.xar.output.XAROutputProperties;
import org.xwiki.filter.xml.output.DefaultResultOutputTarget;
import org.xwiki.job.event.status.JobProgressManager;
import org.xwiki.localization.ContextualLocalizationManager;
import org.xwiki.localization.LocaleUtils;
import org.xwiki.model.EntityType;
import org.xwiki.model.internal.reference.DefaultSymbolScheme;
import org.xwiki.model.internal.reference.LocalStringEntityReferenceSerializer;
import org.xwiki.model.internal.reference.LocalUidStringEntityReferenceSerializer;
import org.xwiki.model.reference.AttachmentReference;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.model.reference.DocumentReferenceResolver;
import org.xwiki.model.reference.EntityReference;
import org.xwiki.model.reference.EntityReferenceProvider;
import org.xwiki.model.reference.EntityReferenceResolver;
import org.xwiki.model.reference.EntityReferenceSerializer;
import org.xwiki.model.reference.LocalDocumentReference;
import org.xwiki.model.reference.ObjectPropertyReference;
import org.xwiki.model.reference.ObjectReference;
import org.xwiki.model.reference.ObjectReferenceResolver;
import org.xwiki.model.reference.SpaceReference;
import org.xwiki.model.reference.WikiReference;
import org.xwiki.query.Query;
import org.xwiki.query.QueryException;
import org.xwiki.query.QueryFilter;
import org.xwiki.rendering.block.Block;
import org.xwiki.rendering.block.Block.Axes;
import org.xwiki.rendering.block.HeaderBlock;
import org.xwiki.rendering.block.LinkBlock;
import org.xwiki.rendering.block.MacroBlock;
import org.xwiki.rendering.block.SectionBlock;
import org.xwiki.rendering.block.XDOM;
import org.xwiki.rendering.block.match.ClassBlockMatcher;
import org.xwiki.rendering.block.match.MacroBlockMatcher;
import org.xwiki.rendering.listener.reference.ResourceReference;
import org.xwiki.rendering.listener.reference.ResourceType;
import org.xwiki.rendering.parser.ContentParser;
import org.xwiki.rendering.parser.MissingParserException;
import org.xwiki.rendering.parser.ParseException;
import org.xwiki.rendering.parser.Parser;
import org.xwiki.rendering.renderer.BlockRenderer;
import org.xwiki.rendering.renderer.printer.DefaultWikiPrinter;
import org.xwiki.rendering.renderer.printer.WikiPrinter;
import org.xwiki.rendering.syntax.Syntax;
import org.xwiki.rendering.syntax.SyntaxFactory;
import org.xwiki.rendering.transformation.RenderingContext;
import org.xwiki.rendering.transformation.TransformationContext;
import org.xwiki.rendering.transformation.TransformationException;
import org.xwiki.rendering.transformation.TransformationManager;
import org.xwiki.rendering.util.ErrorBlockGenerator;
import org.xwiki.security.authorization.ContextualAuthorizationManager;
import org.xwiki.security.authorization.Right;
import org.xwiki.velocity.VelocityManager;
import org.xwiki.xar.internal.model.XarDocumentModel;
import org.xwiki.xml.XMLUtils;
import com.xpn.xwiki.CoreConfiguration;
import com.xpn.xwiki.XWiki;
import com.xpn.xwiki.XWikiConstant;
import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.XWikiException;
import com.xpn.xwiki.api.DocumentSection;
import com.xpn.xwiki.criteria.impl.RevisionCriteria;
import com.xpn.xwiki.doc.merge.MergeConfiguration;
import com.xpn.xwiki.doc.merge.MergeResult;
import com.xpn.xwiki.doc.rcs.XWikiRCSNodeInfo;
import com.xpn.xwiki.internal.AbstractNotifyOnUpdateList;
import com.xpn.xwiki.internal.cache.rendering.RenderingCache;
import com.xpn.xwiki.internal.filter.XWikiDocumentFilterUtils;
import com.xpn.xwiki.internal.merge.MergeUtils;
import com.xpn.xwiki.internal.render.LinkedResourceHelper;
import com.xpn.xwiki.internal.render.OldRendering;
import com.xpn.xwiki.internal.xml.DOMXMLWriter;
import com.xpn.xwiki.internal.xml.XMLWriter;
import com.xpn.xwiki.objects.BaseCollection;
import com.xpn.xwiki.objects.BaseObject;
import com.xpn.xwiki.objects.BaseObjectReference;
import com.xpn.xwiki.objects.BaseProperty;
import com.xpn.xwiki.objects.LargeStringProperty;
import com.xpn.xwiki.objects.ListProperty;
import com.xpn.xwiki.objects.ObjectDiff;
import com.xpn.xwiki.objects.PropertyInterface;
import com.xpn.xwiki.objects.classes.BaseClass;
import com.xpn.xwiki.objects.classes.ListClass;
import com.xpn.xwiki.objects.classes.PropertyClass;
import com.xpn.xwiki.objects.classes.StaticListClass;
import com.xpn.xwiki.objects.classes.TextAreaClass;
import com.xpn.xwiki.store.XWikiAttachmentStoreInterface;
import com.xpn.xwiki.store.XWikiStoreInterface;
import com.xpn.xwiki.store.XWikiVersioningStoreInterface;
import com.xpn.xwiki.user.api.XWikiRightService;
import com.xpn.xwiki.util.Util;
import com.xpn.xwiki.validation.XWikiValidationInterface;
import com.xpn.xwiki.validation.XWikiValidationStatus;
import com.xpn.xwiki.web.EditForm;
import com.xpn.xwiki.web.ObjectAddForm;
import com.xpn.xwiki.web.ObjectPolicyType;
import com.xpn.xwiki.web.Utils;
import com.xpn.xwiki.web.XWikiRequest;
public class XWikiDocument implements DocumentModelBridge, Cloneable
{
private static final Logger LOGGER = LoggerFactory.getLogger(XWikiDocument.class);
/**
* An attachment waiting to be deleted at next document save.
*
* @version $Id: bcbaf47b408c3fed707e420084861746fb335e83 $
* @since 5.2M1
*/
public static class XWikiAttachmentToRemove
{
/**
* @see #getAttachment()
*/
private XWikiAttachment attachment;
/**
* @see #isToRecycleBin()
*/
private boolean toRecycleBin;
/**
* @param attachment the attachment to delete
* @param toRecycleBin true of the attachment should be moved to the recycle bin
*/
public XWikiAttachmentToRemove(XWikiAttachment attachment, boolean toRecycleBin)
{
this.attachment = attachment;
this.toRecycleBin = toRecycleBin;
}
/**
* @return the attachment to delete
*/
public XWikiAttachment getAttachment()
{
return this.attachment;
}
/**
* @return true of the attachment should be moved to the recycle bin
*/
public boolean isToRecycleBin()
{
return this.toRecycleBin;
}
@Override
public int hashCode()
{
return this.attachment.hashCode();
}
@Override
public boolean equals(Object obj)
{
if (obj instanceof XWikiAttachmentToRemove) {
return this.attachment.equals(((XWikiAttachmentToRemove) obj).getAttachment());
}
return false;
}
@Override
public String toString()
{
return this.attachment.toString();
}
}
/**
* Regex Pattern to recognize if there's HTML code in a XWiki page.
*/
private static final Pattern HTML_TAG_PATTERN = Pattern.compile(
"</?+(html|img|a|i|br?|embed|script|form|input|textarea|object|font|li|[dou]l|table|center|hr|p) ?([^>]*+)>");
/**
* Format for passing xproperties references in URLs. General format:
* {@code <space>.<pageClass>_<number>_<propertyName>} (e.g.
* {@code XWiki.XWikiRights_0_member}).
*/
private static final Pattern XPROPERTY_REFERENCE_PATTERN = Pattern.compile("(.+?)_([0-9]+)_(.+)");
public static final EntityReference COMMENTSCLASS_REFERENCE = new LocalDocumentReference("XWiki", "XWikiComments");
public static final EntityReference SHEETCLASS_REFERENCE = new LocalDocumentReference("XWiki", "SheetClass");
public static final int HAS_ATTACHMENTS = 1;
public static final int HAS_OBJECTS = 2;
public static final int HAS_CLASS = 4;
/**
* The name of the key in the XWikiContext which contains the document used to check for programming rights.
*/
public static final String CKEY_SDOC = "sdoc";
/**
* Separator string between database name and space name.
*/
public static final String DB_SPACE_SEP = ":";
/**
* Separator string between space name and page name.
*/
public static final String SPACE_NAME_SEP = ".";
private static final LocalStringEntityReferenceSerializer LOCAL_REFERENCE_SERIALIZER =
new LocalStringEntityReferenceSerializer(new DefaultSymbolScheme());
/**
* Used to resolve a string into a proper Document Reference using the current document's reference to fill the
* blanks.
*/
private static DocumentReferenceResolver<String> getCurrentDocumentReferenceResolver()
{
return Utils.getComponent(DocumentReferenceResolver.TYPE_STRING, "current");
}
/**
* Used to resolve a ResourceReference into a proper Entity Reference using the current document to fill the blanks.
*/
private static EntityReferenceResolver<ResourceReference> getResourceReferenceEntityReferenceResolver()
{
return Utils
.getComponent(new DefaultParameterizedType(null, EntityReferenceResolver.class, ResourceReference.class));
}
private static EntityReferenceResolver<String> getXClassEntityReferenceResolver()
{
return Utils.getComponent(EntityReferenceResolver.TYPE_STRING, "xclass");
}
/**
* Used to resolve a string into a proper Document Reference using the current document's reference to fill the
* blanks, except for the page name for which the default page name is used instead and for the wiki name for which
* the current wiki is used instead of the current document reference's wiki.
*/
private static DocumentReferenceResolver<String> getCurrentMixedDocumentReferenceResolver()
{
return Utils.getComponent(DocumentReferenceResolver.TYPE_STRING, "currentmixed");
}
/**
* Used to normalize references.
*/
private static DocumentReferenceResolver<EntityReference> getCurrentReferenceDocumentReferenceResolver()
{
return Utils.getComponent(DocumentReferenceResolver.TYPE_REFERENCE, "current");
}
/**
* Used to resolve parent references in the way they are stored externally (database, xml, etc), ie relative or
* absolute.
*/
private static EntityReferenceResolver<String> getRelativeEntityReferenceResolver()
{
return Utils.getComponent(EntityReferenceResolver.TYPE_STRING, "relative");
}
/**
* Used to convert a proper Document Reference to string (compact form).
*/
private static EntityReferenceSerializer<String> getCompactEntityReferenceSerializer()
{
return Utils.getComponent(EntityReferenceSerializer.TYPE_STRING, "compact");
}
/**
* Used to convert a Document Reference to string (compact form without the wiki part if it matches the current
* wiki).
*/
private static EntityReferenceSerializer<String> getCompactWikiEntityReferenceSerializer()
{
return Utils.getComponent(EntityReferenceSerializer.TYPE_STRING, "compactwiki");
}
/**
* Used to normalize references.
*/
private static ObjectReferenceResolver<EntityReference> getCurrentReferenceObjectReferenceResolver()
{
return Utils.getComponent(ObjectReferenceResolver.TYPE_REFERENCE, "current");
}
private String title;
/**
* Reference to this document's parent.
* <p>
* Note that we're saving the parent reference as a relative reference instead of an absolute one because We want
* the ability (for example) to create a parent reference relative to the current space or wiki so that a copy of
* this XWikiDocument object would retain that relativity. This is for example useful when copying a Wiki into
* another Wiki so that the copied XWikiDcoument's parent reference points to the new wiki.
*/
private EntityReference parentReference;
private DocumentReference documentReference;
private String content;
private String meta;
private String format;
/**
* First author of the document.
*/
private DocumentReference creatorReference;
/**
* The Author is changed when any part of the document changes (content, objects, attachments).
*/
private DocumentReference authorReference;
/**
* The last user that has changed the document's content (ie not object, attachments). The Content author is only
* changed when the document content changes. Note that Content Author is used to check programming rights on a
* document and this is the reason we need to know the last author who's modified the content since programming
* rights depend on this.
*/
private DocumentReference contentAuthorReference;
private String customClass;
private Date contentUpdateDate;
private Date updateDate;
private Date creationDate;
protected Version version;
private long id = 0;
private boolean mostRecent = true;
private boolean isNew = true;
/**
* The reference to the document that is the template for the current document.
*
* @todo this field is not used yet since it's not currently saved in the database.
*/
private DocumentReference templateDocumentReference;
private Locale locale;
private Locale defaultLocale;
/**
* Indicates whether the document is 'hidden', meaning that it should not be returned in public search results.
* WARNING: this is a temporary hack until the new data model is designed and implemented. No code should rely on or
* use this property, since it will be replaced with a generic metadata.
*/
private boolean hidden = false;
/**
* Comment on the latest modification.
*/
private String comment;
/**
* Wiki syntax supported by this document. This is used to support different syntaxes inside the same wiki. For
* example a page can use the MediaWiki 1.0 syntax while another one uses the XWiki 2.1 syntax.
*/
private Syntax syntax;
/**
* Is latest modification a minor edit.
*/
private boolean isMinorEdit = false;
/**
* Used to make sure the MetaData String is regenerated.
*/
private boolean isContentDirty = true;
/**
* Used to make sure the MetaData String is regenerated.
*/
private boolean isMetaDataDirty = true;
private int elements = HAS_OBJECTS | HAS_ATTACHMENTS;
// Meta Data
private BaseClass xClass;
private String xClassXML;
/**
* Map holding document objects indexed by XClass references (i.e. Document References since a XClass reference
* points to a document). The map is not synchronized, and uses a TreeMap implementation to preserve index ordering
* (consistent sorted order for output to XML, rendering in velocity, etc.)
*/
private Map<DocumentReference, List<BaseObject>> xObjects = new TreeMap<DocumentReference, List<BaseObject>>();
private final List<XWikiAttachment> attachmentList =
new AbstractNotifyOnUpdateList<XWikiAttachment>(new ArrayList<XWikiAttachment>())
{
@Override
public void onUpdate()
{
setMetaDataDirty(true);
}
};
// Caching
private boolean fromCache = false;
private List<BaseObject> xObjectsToRemove = new ArrayList<BaseObject>();
private List<XWikiAttachmentToRemove> attachmentsToRemove = new ArrayList<XWikiAttachmentToRemove>();
/**
* The view template (vm file) to use. When not set the default view template is used.
*
* @see com.xpn.xwiki.web.ViewAction#render(XWikiContext)
*/
private String defaultTemplate;
private String validationScript;
private Object wikiNode;
/**
* We are using a SoftReference which will allow the archive to be discarded by the Garbage collector as long as the
* context is closed (usually during the request)
*/
private SoftReference<XWikiDocumentArchive> archive;
private XWikiStoreInterface store;
/**
* @see #getOriginalDocument()
*/
private XWikiDocument originalDocument;
/**
* Used to display the title and the content of this document. Do not inject the component here to avoid any simple
* new XWikiDocument to cause many useless initialization, in particular, during initialization of the stub context
* and other fake documents in tests.
*/
private DocumentDisplayer documentDisplayer;
/**
* @see #getDefaultEntityReferenceSerializer()
*/
private EntityReferenceSerializer<String> defaultEntityReferenceSerializer;
/**
* @see #getExplicitDocumentReferenceResolver()
*/
private DocumentReferenceResolver<String> explicitDocumentReferenceResolver;
/**
* @see #getExplicitReferenceDocumentReferenceResolver()
*/
private DocumentReferenceResolver<EntityReference> explicitReferenceDocumentReferenceResolver;
/**
* @see #getUidStringEntityReferenceSerializer()
*/
private EntityReferenceSerializer<String> uidStringEntityReferenceSerializer;
private Provider<OldRendering> oldRenderingProvider;
private JobProgressManager progress;
/**
* @see #getSyntaxFactory()
*/
private SyntaxFactory syntaxFactory;
private ContextualLocalizationManager localization;
/**
* The document structure expressed as a tree of Block objects. We store it for performance reasons since parsing is
* a costly operation that we don't want to repeat whenever some code ask for the XDOM information.
*/
private XDOM xdomCache;
/**
* Use to store rendered documents in #getRenderedContent(). Do not inject the component here to avoid any simple
* new XWikiDocument to cause many useless initialization, in particular, during initialization of the stub context
* and other fake documents in tests.
*/
private RenderingCache renderingCache;
/**
* Cache the parent reference resolved as an absolute reference for improved performance (so that we don't have to
* resolve the relative reference every time getParentReference() is called.
*/
private DocumentReference parentReferenceCache;
/**
* @see #getKey()
*/
private String keyCache;
/**
* @see #getLocalKey()
*/
private String localKeyCache;
private RenderingContext renderingContext;
/**
* @since 2.2M1
*/
public XWikiDocument(DocumentReference reference)
{
init(reference);
}
/**
* @since 6.2
*/
public XWikiDocument(DocumentReference reference, Locale locale)
{
init(reference);
this.locale = locale;
}
/**
* @deprecated since 2.2M1 use {@link #XWikiDocument(org.xwiki.model.reference.DocumentReference)} instead
*/
@Deprecated
public XWikiDocument()
{
this(null);
}
/**
* Constructor that specifies the local document identifier: space name, document name. {@link #setDatabase(String)}
* must be called afterwards to specify the wiki name.
*
* @param space the space this document belongs to
* @param name the name of the document
* @deprecated since 2.2M1 use {@link #XWikiDocument(org.xwiki.model.reference.DocumentReference)} instead
*/
@Deprecated
public XWikiDocument(String space, String name)
{
this(null, space, name);
}
/**
* Constructor that specifies the full document identifier: wiki name, space name, document name.
*
* @param wiki The wiki this document belongs to.
* @param space The space this document belongs to.
* @param name The name of the document (can contain either the page name or the space and page name)
* @deprecated since 2.2M1 use {@link #XWikiDocument(org.xwiki.model.reference.DocumentReference)} instead
*/
@Deprecated
public XWikiDocument(String wiki, String space, String name)
{
// We allow to specify the space in the name (eg name = "space.page"). In this case the passed space is
// ignored.
// Build an entity reference that will serve as a current context reference against which to resolve if the
// passed name doesn't contain a space.
EntityReference contextReference = null;
if (!StringUtils.isEmpty(space)) {
contextReference = new EntityReference(space, EntityType.SPACE);
}
DocumentReference reference = getCurrentDocumentReferenceResolver().resolve(name, contextReference);
if (!StringUtils.isEmpty(wiki)) {
reference = reference.replaceParent(reference.getWikiReference(), new WikiReference(wiki));
}
init(reference);
}
/**
* Used to resolve a string into a proper Document Reference.
*/
private DocumentReferenceResolver<String> getExplicitDocumentReferenceResolver()
{
if (this.explicitDocumentReferenceResolver == null) {
this.explicitDocumentReferenceResolver =
Utils.getComponent(DocumentReferenceResolver.TYPE_STRING, "explicit");
}
return this.explicitDocumentReferenceResolver;
}
/**
* Used to normalize references.
*/
private DocumentReferenceResolver<EntityReference> getExplicitReferenceDocumentReferenceResolver()
{
if (this.explicitReferenceDocumentReferenceResolver == null) {
this.explicitReferenceDocumentReferenceResolver =
Utils.getComponent(DocumentReferenceResolver.TYPE_REFERENCE, "explicit");
}
return this.explicitReferenceDocumentReferenceResolver;
}
/**
* Used to convert a proper Document Reference to string (standard form).
*/
private EntityReferenceSerializer<String> getDefaultEntityReferenceSerializer()
{
if (this.defaultEntityReferenceSerializer == null) {
this.defaultEntityReferenceSerializer = Utils.getComponent(EntityReferenceSerializer.TYPE_STRING);
}
return this.defaultEntityReferenceSerializer;
}
/**
* Used to compute document identifier.
*/
private EntityReferenceSerializer<String> getUidStringEntityReferenceSerializer()
{
if (this.uidStringEntityReferenceSerializer == null) {
this.uidStringEntityReferenceSerializer = Utils.getComponent(EntityReferenceSerializer.TYPE_STRING, "uid");
}
return this.uidStringEntityReferenceSerializer;
}
/**
* Used to create proper {@link Syntax} objects.
*/
private SyntaxFactory getSyntaxFactory()
{
if (this.syntaxFactory == null) {
this.syntaxFactory = Utils.getComponent((Type) SyntaxFactory.class);
}
return this.syntaxFactory;
}
private ContextualLocalizationManager getLocalization()
{
if (this.localization == null) {
this.localization = Utils.getComponent(ContextualLocalizationManager.class);
}
return this.localization;
}
private OldRendering getOldRendering()
{
if (this.oldRenderingProvider == null) {
this.oldRenderingProvider = Utils.getComponent(OldRendering.TYPE_PROVIDER);
}
return this.oldRenderingProvider.get();
}
private JobProgressManager getProgress()
{
if (this.progress == null) {
this.progress = Utils.getComponent(JobProgressManager.class);
}
return this.progress;
}
private String localizePlainOrKey(String key, Object... parameters)
{
return StringUtils.defaultString(getLocalization().getTranslationPlain(key, parameters), key);
}
public XWikiStoreInterface getStore(XWikiContext context)
{
return context.getWiki().getStore();
}
public XWikiAttachmentStoreInterface getAttachmentStore(XWikiContext context)
{
return context.getWiki().getAttachmentStore();
}
public XWikiVersioningStoreInterface getVersioningStore(XWikiContext context)
{
return context.getWiki().getVersioningStore();
}
public XWikiStoreInterface getStore()
{
return this.store;
}
public void setStore(XWikiStoreInterface store)
{
this.store = store;
}
private RenderingContext getRenderingContext()
{
if (this.renderingContext == null) {
this.renderingContext = Utils.getComponent(RenderingContext.class);
}
return this.renderingContext;
}
/**
* Helper to produce and cache a local uid serialization of this document reference, including the language. Only
* translated document will have language appended.
*
* @return a unique name (in a wiki) (5:space4:name2:lg)
*/
private String getLocalKey()
{
if (this.localKeyCache == null) {
this.localKeyCache =
LocalUidStringEntityReferenceSerializer.INSTANCE.serialize(getDocumentReferenceWithLocale());
}
return this.localKeyCache;
}
/**
* Helper to produce and cache a uid serialization of this document reference, including the language. Only
* translated document will have language appended.
*
* @return a unique name (8:wikiname5:space4:name2:lg or 8:wikiname5:space4:name)
* @since 4.0M1
*/
public String getKey()
{
if (this.keyCache == null) {
this.keyCache = getUidStringEntityReferenceSerializer().serialize(getDocumentReferenceWithLocale());
}
return this.keyCache;
}
@Override
public int hashCode()
{
return (int) Util.getHash(getLocalKey());
}
/**
* @return the unique id used to represent the document, as a number. This id is technical and is equivalent to the
* Document Reference + the language of the Document. This technical id should only be used for the storage
* layer and all user APIs should instead use Document Reference and language as they are model-related
* while the id isn't (it's purely technical).
*/
public long getId()
{
// TODO: Ensure uniqueness of the generated id
// The implementation doesn't guarantee a unique id since it uses a hashing method which never guarantee
// uniqueness. However, the hash algorithm is really unlikely to collide in a given wiki. This needs to be
// fixed to produce a real unique id since otherwise we can have clashes in the database.
// Note: We don't use the wiki name in the document id's computation. The main historical reason is so
// that all things saved in a given wiki's database are always stored relative to that wiki so that
// changing that wiki's name is simpler.
this.id = Util.getHash(getLocalKey());
return this.id;
}
/**
* @see #getId()
*/
public void setId(long id)
{
this.id = id;
}
/**
* Return the full local space reference. For example a document located in sub-space <code>space11</code> of space
* <code>space1</code> will return <code>space1.space11</code>.
* <p>
* Note that this method cannot be removed for now since it's used by Hibernate for saving a XWikiDocument.
*
* @return the local reference the space of the document as String
* @deprecated since 2.2M1 used {@link #getDocumentReference()} instead
*/
@Deprecated
public String getSpace()
{
return LOCAL_REFERENCE_SERIALIZER.serialize(getDocumentReference().getLastSpaceReference());
}
/**
* Set the full local space reference.
* <p>
* Note that this method cannot be removed for now since it's used by Hibernate for loading a XWikiDocument.
*
* @see #getSpace()
* @deprecated since 2.2M1 used {@link #setDocumentReference(DocumentReference)} instead
*/
@Deprecated
public void setSpace(String spaces)
{
if (spaces != null) {
DocumentReference reference = getDocumentReference();
EntityReference spaceReference = getRelativeEntityReferenceResolver().resolve(spaces, EntityType.SPACE);
spaceReference = spaceReference.appendParent(getDocumentReference().getWikiReference());
setDocumentReferenceInternal(
new DocumentReference(reference.getName(), new SpaceReference(spaceReference)));
}
}
/**
* Note that this method cannot be removed for now since it's used by Hibernate for saving a XWikiDocument.
*
* @return the name of the space of the document
* @see #getSpace()
* @deprecated use {@link #getDocumentReference()} instead
*/
@Deprecated
public String getWeb()
{
return getSpace();
}
/**
* Note that this method cannot be removed for now since it's used by Hibernate for loading a XWikiDocument.
*
* @deprecated use {@link #setDocumentReference(DocumentReference)} instead
*/
@Deprecated
public void setWeb(String space)
{
setSpace(space);
}
@Override
public String getVersion()
{
return getRCSVersion().toString();
}
public void setVersion(String version)
{
if (!StringUtils.isEmpty(version)) {
this.version = new Version(version);
}
}
public Version getRCSVersion()
{
if (this.version == null) {
return new Version("1.1");
}
return this.version;
}
public void setRCSVersion(Version version)
{
this.version = version;
}
/**
* @return the copy of this XWikiDocument instance before any modification was made to it. It is reset to the actual
* values when the document is saved in the database. This copy is used for finding out differences made to
* this document (useful for example to send the correct notifications to document change listeners).
*/
@Override
public XWikiDocument getOriginalDocument()
{
return this.originalDocument;
}
/**
* @param originalDocument the original document representing this document instance before any change was made to
* it, prior to the last time it was saved
* @see #getOriginalDocument()
*/
public void setOriginalDocument(XWikiDocument originalDocument)
{
this.originalDocument = originalDocument;
}
/**
* @return the parent reference or null if the parent is not set
* @since 2.2M1
*/
public DocumentReference getParentReference()
{
// Ensure we always return absolute document references for the parent since we always want well-constructed
// references and since we store the parent reference as relative internally.
if (this.parentReferenceCache == null && getRelativeParentReference() != null) {
this.parentReferenceCache = getExplicitReferenceDocumentReferenceResolver()
.resolve(getRelativeParentReference(), getDocumentReference());
}
return this.parentReferenceCache;
}
/**
* Note that this method cannot be removed for now since it's used by Hibernate for saving a XWikiDocument.
*
* @return the parent reference stored in the database, which is relative to this document, or an empty string ("")
* if the parent is not set
* @see #getParentReference()
* @deprecated since 2.2M1 use {@link #getParentReference()} instead
*/
@Deprecated
public String getParent()
{
String parentReferenceAsString;
if (getParentReference() != null) {
parentReferenceAsString = getDefaultEntityReferenceSerializer().serialize(getRelativeParentReference());
} else {
parentReferenceAsString = "";
}
return parentReferenceAsString;
}
/**
* @deprecated since 2.2M1 use {@link #getParentReference()} instead
*/
@Deprecated
public XWikiDocument getParentDoc()
{
return new XWikiDocument(getParentReference());
}
/**
* @since 2.2.3
*/
public void setParentReference(EntityReference parentReference)
{
if (!Objects.equals(getRelativeParentReference(), parentReference)) {
this.parentReference = parentReference;
// Clean the absolute parent reference cache to rebuild it next time getParentReference is called.
this.parentReferenceCache = null;
setMetaDataDirty(true);
}
}
/**
* Note that this method cannot be removed for now since it's used by Hibernate for loading a XWikiDocument.
*
* @param parent the reference of the parent relative to the document
* @deprecated since 2.2M1, use {@link #setParentReference(EntityReference)} instead
*/
@Deprecated
public void setParent(String parent)
{
// If the passed parent is an empty string we also need to set the reference to null. The reason is that
// in the database we store "" when the parent is empty and thus when Hibernate loads this class it'll call
// setParent with "" if the parent had not been set when saved.
if (StringUtils.isEmpty(parent)) {
setParentReference((EntityReference) null);
} else {
setParentReference(getRelativeEntityReferenceResolver().resolve(parent, EntityType.DOCUMENT));
}
}
@Override
public String getContent()
{
return this.content;
}
public void setContent(String content)
{
if (content == null) {
content = "";
}
boolean notEqual = !content.equals(this.content);
this.content = content;
if (notEqual) {
// invalidate parsed xdom
this.xdomCache = null;
setContentDirty(true);
setWikiNode(null);
}
}
public void setContent(XDOM content) throws XWikiException
{
setContent(renderXDOM(content, getSyntax()));
}
/**
* @return the default rendering cache
*/
private RenderingCache getRenderingCache()
{
if (this.renderingCache == null) {
this.renderingCache = Utils.getComponent((Type) RenderingCache.class);
}
return this.renderingCache;
}
/**
* @return the configured document displayer
*/
private DocumentDisplayer getDocumentDisplayer()
{
if (this.documentDisplayer == null) {
this.documentDisplayer = Utils.getComponent((Type) DocumentDisplayer.class, "configured");
}
return this.documentDisplayer;
}
private Syntax getOutputSyntax()
{
return getRenderingContext().getTargetSyntax();
}
/**
* Parse, execute and render the document.
*
* @param targetSyntax the syntax to use to render the document
* @param executionContextIsolated see {@link DocumentDisplayerParameters#isExecutionContextIsolated()}
* @param transformationContextIsolated see {@link DocumentDisplayerParameters#isTransformationContextIsolated()}
* @param transformationContextRestricted see
* {@link DocumentDisplayerParameters#isTransformationContextRestricted()}
* @param translate get translated content of the document
* @return the result of the document execution rendered in the passed syntax
* @throws XWikiException when failing to display the document
*/
private String display(Syntax targetSyntax, boolean executionContextIsolated, boolean transformationContextIsolated,
boolean transformationContextRestricted, boolean translate) throws XWikiException
{
// Note: We are currently duplicating code from getRendered signature because some calling
// code is expecting that the rendering will happen in the calling document's context and not in this
// document's context. For example this is true for the Admin page, see
// https://jira.xwiki.org/browse/XWIKI-4274 for more details.
getProgress().startStep(this, "document.progress.render", "Render document [{}] in syntax [{}]",
getDocumentReference(), targetSyntax);
try {
getProgress().pushLevelProgress(3, getDocumentReference());
getProgress().startStep(getDocumentReference(), "document.progress.render.translatedcontent",
"Get translated content");
XWikiContext xcontext = getXWikiContext();
XWikiDocument tdoc = translate ? getTranslatedDocument(xcontext) : this;
String translatedContent = tdoc.getContent();
getProgress().startStep(getDocumentReference(), "document.progress.render.cache",
"Try to get content from the cache");
String renderedContent = getRenderingCache().getRenderedContent(tdoc.getDocumentReferenceWithLocale(),
translatedContent, xcontext);
if (renderedContent == null) {
getProgress().startStep(getDocumentReference(), "document.progress.render.execute", "Execute content");
// Configure display
DocumentDisplayerParameters parameters = new DocumentDisplayerParameters();
parameters.setExecutionContextIsolated(executionContextIsolated);
parameters.setTransformationContextIsolated(transformationContextIsolated);
parameters.setTransformationContextRestricted(transformationContextRestricted);
// Render the translated content (matching the current language) using this document's syntax.
parameters.setContentTranslated(tdoc != this);
parameters.setTargetSyntax(targetSyntax);
// Execute display
XDOM contentXDOM = getDocumentDisplayer().display(this, parameters);
// Render the result
renderedContent = renderXDOM(contentXDOM, targetSyntax);
getRenderingCache().setRenderedContent(getDocumentReference(), translatedContent, renderedContent,
xcontext);
}
return renderedContent;
} finally {
getProgress().popLevelProgress(getDocumentReference());
getProgress().endStep(this);
}
}
public String getRenderedContent(Syntax targetSyntax, XWikiContext context) throws XWikiException
{
return getRenderedContent(targetSyntax, true, context);
}
/**
* @since 8.4RC1
*/
public String getRenderedContent(boolean transformationContextIsolated, XWikiContext context) throws XWikiException
{
return getRenderedContent(getOutputSyntax(), transformationContextIsolated, context);
}
public String getRenderedContent(Syntax targetSyntax, boolean transformationContextIsolated, XWikiContext context)
throws XWikiException
{
// Make sure the context secure document is the current document so that it's executed with its own
// rights
Object currrentSdoc = context.get("sdoc");
try {
// If we execute a translation use translated document as secure document
XWikiDocument tdoc = getTranslatedDocument(context);
context.put("sdoc", tdoc);
return display(targetSyntax, false, transformationContextIsolated, false, true);
} finally {
context.put("sdoc", currrentSdoc);
}
}
public String getRenderedContent(XWikiContext context) throws XWikiException
{
return getRenderedContent(getOutputSyntax(), context);
}
/**
* @param text the text to render
* @param syntaxId the id of the Syntax used by the passed text (e.g. {@code xwiki/2.1})
* @param context the XWiki Context object
* @return the given text rendered in the context of this document using the passed Syntax
* @since 1.6M1
*/
public String getRenderedContent(String text, String syntaxId, XWikiContext context)
{
return getRenderedContent(text, syntaxId, getOutputSyntax().toIdString(), context);
}
/**
* @param text the text to render
* @param syntaxId the id of the Syntax used by the passed text (e.g. {@code xwiki/2.1})
* @param restrictedTransformationContext see {@link DocumentDisplayerParameters#isTransformationContextRestricted}.
* @param context the XWiki Context object
* @return the given text rendered in the context of this document using the passed Syntax
* @since 4.2M1
*/
public String getRenderedContent(String text, String syntaxId, boolean restrictedTransformationContext,
XWikiContext context)
{
return getRenderedContent(text, syntaxId, getOutputSyntax().toIdString(), restrictedTransformationContext,
context);
}
/**
* @param text the text to render
* @param syntaxId the id of the Syntax used by the passed text (e.g. {@code xwiki/2.1})
* @param restrictedTransformationContext see {@link DocumentDisplayerParameters#isTransformationContextRestricted}.
* @param sDocument the {@link XWikiDocument} to use as secure document, if null keep the current one
* @param context the XWiki Context object
* @return the given text rendered in the context of this document using the passed Syntax
* @since 8.3
*/
public String getRenderedContent(String text, String syntaxId, boolean restrictedTransformationContext,
XWikiDocument sDocument, XWikiContext context)
{
return getRenderedContent(text, syntaxId, getOutputSyntax().toIdString(), restrictedTransformationContext,
sDocument, context);
}
/**
* @param text the text to render
* @param sourceSyntaxId the id of the Syntax used by the passed text (e.g. {@code xwiki/2.1})
* @param targetSyntaxId the id of the syntax in which to render the document content
* @param context the XWiki context
* @return the given text rendered in the context of this document using the passed Syntax
* @since 2.0M3
*/
public String getRenderedContent(String text, String sourceSyntaxId, String targetSyntaxId, XWikiContext context)
{
return getRenderedContent(text, sourceSyntaxId, targetSyntaxId, false, context);
}
/**
* @param text the text to render
* @param sourceSyntaxId the id of the Syntax used by the passed text (e.g. {@code xwiki/2.1})
* @param targetSyntaxId the id of the syntax in which to render the document content
* @param restrictedTransformationContext see {@link DocumentDisplayerParameters#isTransformationContextRestricted}.
* @param context the XWiki context
* @return the given text rendered in the context of this document using the passed Syntax
* @since 4.2M1
*/
public String getRenderedContent(String text, String sourceSyntaxId, String targetSyntaxId,
boolean restrictedTransformationContext, XWikiContext context)
{
return getRenderedContent(text, sourceSyntaxId, targetSyntaxId, restrictedTransformationContext, null, context);
}
/**
* @param text the text to render
* @param sourceSyntaxId the id of the Syntax used by the passed text (e.g. {@code xwiki/2.1})
* @param targetSyntaxId the id of the syntax in which to render the document content
* @param restrictedTransformationContext see {@link DocumentDisplayerParameters#isTransformationContextRestricted}.
* @param sDocument the {@link XWikiDocument} to use as secure document, if null keep the current one
* @param context the XWiki context
* @return the given text rendered in the context of this document using the passed Syntax
* @since 8.3
*/
public String getRenderedContent(String text, String sourceSyntaxId, String targetSyntaxId,
boolean restrictedTransformationContext, XWikiDocument sDocument, XWikiContext context)
{
Map<String, Object> backup = null;
getProgress().startStep(this, "document.progress.renderText",
"Execute content [{}] in the context of document [{}]",
StringUtils.substring(text, 0, 100) + (text.length() >= 100 ? "..." : ""), getDocumentReference());
XWikiDocument currentSDocument = (XWikiDocument) context.get(CKEY_SDOC);
try {
// We have to render the given text in the context of this document. Check if this document is already
// on the context (same Java object reference). We don't check if the document references are equal
// because this document can have temporary changes that are not present on the context document even if
// it has the same document reference.
if (context.getDoc() != this) {
backup = new HashMap<>();
backupContext(backup, context);
setAsContextDoc(context);
}
// Make sure to execute the document with the right of the provided sdocument's author
if (sDocument != null) {
context.put(CKEY_SDOC, sDocument);
}
// Reuse this document's reference so that the Velocity macro name-space is computed based on it.
XWikiDocument fakeDocument = new XWikiDocument(getDocumentReference());
fakeDocument.setSyntax(getSyntaxFactory().createSyntaxFromIdString(sourceSyntaxId));
fakeDocument.setContent(text);
// We don't let displayer take care of the context isolation because we don't want the fake document to be
// context document
return fakeDocument.display(getSyntaxFactory().createSyntaxFromIdString(targetSyntaxId), false, true,
restrictedTransformationContext, false);
} catch (Exception e) {
// Failed to render for some reason. This method should normally throw an exception but this
// requires changing the signature of calling methods too.
LOGGER.warn("Failed to render content [" + text + "]", e);
} finally {
if (backup != null) {
restoreContext(backup, context);
}
context.put(CKEY_SDOC, currentSDocument);
getProgress().endStep(this);
}
return "";
}
public String getEscapedContent(XWikiContext context) throws XWikiException
{
return XMLUtils.escape(getTranslatedContent(context));
}
/**
* Note that this method cannot be removed for now since it's used by Hibernate for saving a XWikiDocument.
*
* @deprecated since 2.2M1 used {@link #getDocumentReference()} instead
*/
@Deprecated
public String getName()
{
return getDocumentReference().getName();
}
/**
* Note that this method cannot be removed for now since it's used by Hibernate for loading a XWikiDocument.
*
* @deprecated since 2.2M1 used {@link #setDocumentReference(DocumentReference)} instead
*/
@Deprecated
public void setName(String name)
{
if (name != null) {
DocumentReference reference = getDocumentReference();
// TODO: ensure that other parameters are copied properly
setDocumentReferenceInternal(
new DocumentReference(name, new SpaceReference(reference.getParent()), reference.getLocale()));
}
}
@Override
public DocumentReference getDocumentReference()
{
return this.documentReference;
}
/**
* @return the {@link DocumentReference} of the document also containing the document {@link Locale}
* @since 5.3M2
*/
public DocumentReference getDocumentReferenceWithLocale()
{
return new DocumentReference(this.documentReference, getLocale());
}
/**
* @return the document's space + page name (eg "space.page")
* @deprecated since 2.2M1 use {@link #getDocumentReference()} instead
*/
@Deprecated
@Override
public String getFullName()
{
return LOCAL_REFERENCE_SERIALIZER.serialize(getDocumentReference());
}
/**
* @return the docoument's wiki + space + page name (eg "wiki:space.page")
* @deprecated since 2.2M1 use {@link #getDocumentReference()} instead
*/
@Deprecated
public String getPrefixedFullName()
{
return getDefaultEntityReferenceSerializer().serialize(getDocumentReference());
}
/**
* @since 2.2M1
* @deprecated since 2.2.3 don't change the reference of a document once it's been constructed. Instead you can
* clone the doc, rename it or copy it.
*/
@Deprecated
public void setDocumentReference(DocumentReference reference)
{
// Don't allow setting a null reference for now, ie. don't do anything to preserve backward compatibility
// with previous behavior (i.e. {@link #setFullName}.
if (reference != null) {
// Retro compatibility, make sure <code>this.documentReference</code> does not contain the Locale (for now)
DocumentReference referenceWithoutLocale =
reference.getLocale() != null ? new DocumentReference(reference, null) : reference;
if (!referenceWithoutLocale.equals(getDocumentReference())) {
setDocumentReferenceInternal(referenceWithoutLocale);
}
}
}
private void setDocumentReferenceInternal(DocumentReference reference)
{
this.documentReference = reference;
setMetaDataDirty(true);
// Clean various caches
this.keyCache = null;
this.localKeyCache = null;
this.parentReferenceCache = null;
}
/**
* @deprecated since 2.2M1 use {@link #setDocumentReference(org.xwiki.model.reference.DocumentReference)} instead
*/
@Deprecated
public void setFullName(String name)
{
setFullName(name, null);
}
/**
* @deprecated since 2.2M1 use {@link #setDocumentReference(org.xwiki.model.reference.DocumentReference)} instead
*/
@Deprecated
public void setFullName(String fullName, XWikiContext context)
{
// We ignore the passed full name if it's null to be backward compatible with previous behaviors and to be
// consistent with {@link #setName} and {@link #setSpace}.
if (fullName != null) {
// Note: We use the CurrentMixed Resolver since we want to use the default page name if the page isn't
// specified in the passed string, rather than use the current document's page name.
setDocumentReference(getCurrentMixedDocumentReferenceResolver().resolve(fullName));
}
}
/**
* {@inheritDoc}
*
* @see DocumentModelBridge#getWikiName()
* @deprecated since 2.2M1 use {@link #getDocumentReference()} instead
*/
@Deprecated
@Override
public String getWikiName()
{
return getDatabase();
}
/**
* {@inheritDoc}
*
* @see DocumentModelBridge#getSpaceName()
* @deprecated since 2.2M1 use {@link #getDocumentReference()} instead
*/
@Deprecated
@Override
public String getSpaceName()
{
return this.getSpace();
}
/**
* {@inheritDoc}
*
* @see DocumentModelBridge#getSpaceName()
* @deprecated since 2.2M1 use {@link #getDocumentReference()} instead
*/
@Deprecated
@Override
public String getPageName()
{
return this.getName();
}
@Override
public String getTitle()
{
return (this.title != null) ? this.title : "";
}
/**
* Get the rendered version of the document title. The title is extracted and then Velocity is applied on it and
* it's then rendered using the passed Syntax. The following logic is used to extract the title:
* <ul>
* <li>If a Sheet is specified for the document and this Sheet document contains a non empty title then it's
* used</li>
* <li>If not and the document's title is specified then it's used</li>
* <li>If not and if the title compatibility mode is turned on ({@code xwiki.title.compatibility=1} in
* {@code xwiki.cfg}) then an attempt is made to extract the title from the first heading found in the document's
* content</li>
* <li>If not, then at last resort the page name is returned</li>
* </ul>
*
* @param outputSyntax the syntax to render to; this is not taken into account for XWiki 1.0 syntax
* @param context the XWiki context
* @return the rendered version of the document title
*/
public String getRenderedTitle(Syntax outputSyntax, XWikiContext context)
{
DocumentDisplayerParameters parameters = new DocumentDisplayerParameters();
parameters.setTitleDisplayed(true);
parameters.setExecutionContextIsolated(true);
parameters.setTargetSyntax(outputSyntax);
try {
XDOM titleXDOM = getDocumentDisplayer().display(this, parameters);
return renderXDOM(titleXDOM, outputSyntax);
} catch (Exception e) {
// We've failed to extract the Document's title or to render it. We log an error but we use the page name
// as the returned title in order to not generate errors in lots of places in the wiki (e.g. Activity
// Stream, menus, etc). The title is used in a lots of places...
LOGGER.error("Failed to render title for [{}]", getDocumentReference(), e);
return getDocumentReference().getName();
}
}
/**
* Similar to {@link #getRenderedTitle(Syntax, XWikiContext)} but the output Syntax used is XHTML 1.0 unless the
* current skin defines another output Syntax in which case it's the one used.
*
* @param context the XWiki context
* @return the rendered version of the document title
*/
public String getRenderedTitle(XWikiContext context)
{
return getRenderedTitle(getOutputSyntax(), context);
}
public void setTitle(String title)
{
if (title != null && !title.equals(this.title)) {
// Document titles usually contain velocity script, so it is not enough to set the metadata dirty, since we
// want to content author to be updated for programming or script rights to be updated.
setContentDirty(true);
}
this.title = title;
}
public String getFormat()
{
return this.format != null ? this.format : "";
}
public void setFormat(String format)
{
this.format = format;
if (!format.equals(this.format)) {
setMetaDataDirty(true);
}
}
/**
* @param userString the user {@link String} to convert to {@link DocumentReference}
* @return the user as {@link DocumentReference}
*/
private DocumentReference userStringToReference(String userString)
{
DocumentReference userReference;
if (StringUtils.isEmpty(userString)) {
userReference = null;
} else {
userReference = getExplicitReferenceDocumentReferenceResolver().resolve(
getXClassEntityReferenceResolver().resolve(userString, EntityType.DOCUMENT), getDocumentReference());
if (userReference.getName().equals(XWikiRightService.GUEST_USER)) {
userReference = null;
}
}
return userReference;
}
/**
* @param userReference the user {@link DocumentReference} to convert to {@link String}
* @return the user as String
*/
private String userReferenceToString(DocumentReference userReference)
{
String userString;
if (userReference != null) {
userString = getCompactWikiEntityReferenceSerializer().serialize(userReference, getDocumentReference());
} else {
userString = XWikiRightService.GUEST_USER_FULLNAME;
}
return userString;
}
/**
* @since 3.0M3
*/
public DocumentReference getAuthorReference()
{
return this.authorReference;
}
/**
* @since 3.0M3
*/
public void setAuthorReference(DocumentReference authorReference)
{
if (ObjectUtils.notEqual(authorReference, getAuthorReference())) {
setMetaDataDirty(true);
}
this.authorReference = authorReference;
// Log this since it's probably a mistake so that we find who is doing bad things
if (this.authorReference != null && this.authorReference.getName().equals(XWikiRightService.GUEST_USER)) {
LOGGER.warn("A reference to XWikiGuest user has been set instead of null. This is probably a mistake.",
new Exception("See stack trace"));
}
}
/**
* Note that this method cannot be removed for now since it's used by Hibernate for saving a XWikiDocument.
*
* @deprecated since 3.0M3 use {@link #getAuthorReference()} instead
*/
@Deprecated
public String getAuthor()
{
return userReferenceToString(getAuthorReference());
}
/**
* Note that this method cannot be removed for now since it's used by Hibernate for loading a XWikiDocument.
*
* @deprecated since 3.0M3 use {@link #setAuthorReference} instead
*/
@Deprecated
public void setAuthor(String author)
{
setAuthorReference(userStringToReference(author));
}
/**
* @since 3.0M3
*/
@Override
public DocumentReference getContentAuthorReference()
{
return this.contentAuthorReference;
}
/**
* @since 3.0M3
*/
public void setContentAuthorReference(DocumentReference contentAuthorReference)
{
if (ObjectUtils.notEqual(contentAuthorReference, getContentAuthorReference())) {
setMetaDataDirty(true);
}
this.contentAuthorReference = contentAuthorReference;
// Log this since it's probably a mistake so that we find who is doing bad things
if (this.contentAuthorReference != null
&& this.contentAuthorReference.getName().equals(XWikiRightService.GUEST_USER)) {
LOGGER.warn("A reference to XWikiGuest user has been set instead of null. This is probably a mistake.",
new Exception("See stack trace"));
}
}
/**
* Note that this method cannot be removed for now since it's used by Hibernate for saving a XWikiDocument.
*
* @deprecated since 3.0M3 use {@link #getContentAuthorReference()} instead
*/
@Deprecated
public String getContentAuthor()
{
return userReferenceToString(getContentAuthorReference());
}
/**
* Note that this method cannot be removed for now since it's used by Hibernate for loading a XWikiDocument.
*
* @deprecated since 3.0M3 use {@link #setContentAuthorReference} instead
*/
@Deprecated
public void setContentAuthor(String contentAuthor)
{
setContentAuthorReference(userStringToReference(contentAuthor));
}
/**
* @since 3.0M3
*/
public DocumentReference getCreatorReference()
{
return this.creatorReference;
}
/**
* @since 3.0M3
*/
public void setCreatorReference(DocumentReference creatorReference)
{
if (ObjectUtils.notEqual(creatorReference, getCreatorReference())) {
setMetaDataDirty(true);
}
this.creatorReference = creatorReference;
// Log this since it's probably a mistake so that we find who is doing bad things
if (this.creatorReference != null && this.creatorReference.getName().equals(XWikiRightService.GUEST_USER)) {
LOGGER.warn("A reference to XWikiGuest user has been set instead of null. This is probably a mistake.",
new Exception("See stack trace"));
}
}
/**
* Note that this method cannot be removed for now since it's used by Hibernate for saving a XWikiDocument.
*
* @deprecated since 3.0M2 use {@link #getCreatorReference()} instead
*/
@Deprecated
public String getCreator()
{
return userReferenceToString(getCreatorReference());
}
/**
* Note that this method cannot be removed for now since it's used by Hibernate for loading a XWikiDocument.
*
* @deprecated since 3.0M2 use {@link #setCreatorReference} instead
*/
@Deprecated
public void setCreator(String creator)
{
setCreatorReference(userStringToReference(creator));
}
public Date getDate()
{
if (this.updateDate == null) {
return new Date();
} else {
return this.updateDate;
}
}
public void setDate(Date date)
{
if ((date != null) && (!date.equals(this.updateDate))) {
setMetaDataDirty(true);
}
// Make sure we drop milliseconds for consistency with the database
if (date != null) {
date.setTime((date.getTime() / 1000) * 1000);
}
this.updateDate = date;
}
public Date getCreationDate()
{
if (this.creationDate == null) {
return new Date();
} else {
return this.creationDate;
}
}
public void setCreationDate(Date date)
{
if ((date != null) && (!date.equals(this.creationDate))) {
setMetaDataDirty(true);
}
// Make sure we drop milliseconds for consistency with the database
if (date != null) {
date.setTime((date.getTime() / 1000) * 1000);
}
this.creationDate = date;
}
public Date getContentUpdateDate()
{
if (this.contentUpdateDate == null) {
return new Date();
} else {
return this.contentUpdateDate;
}
}
public void setContentUpdateDate(Date date)
{
if ((date != null) && (!date.equals(this.contentUpdateDate))) {
setMetaDataDirty(true);
}
// Make sure we drop milliseconds for consistency with the database
if (date != null) {
date.setTime((date.getTime() / 1000) * 1000);
}
this.contentUpdateDate = date;
}
public String getMeta()
{
return this.meta;
}
public void setMeta(String meta)
{
if (meta == null) {
if (this.meta != null) {
setMetaDataDirty(true);
}
} else if (!meta.equals(this.meta)) {
setMetaDataDirty(true);
}
this.meta = meta;
}
public void appendMeta(String meta)
{
StringBuilder buf = new StringBuilder(this.meta);
buf.append(meta);
buf.append("\n");
this.meta = buf.toString();
setMetaDataDirty(true);
}
public boolean isContentDirty()
{
return this.isContentDirty;
}
public void incrementVersion()
{
if (this.version == null) {
this.version = new Version("1.1");
} else {
if (isMinorEdit()) {
this.version = this.version.next();
} else {
this.version = this.version.getBranchPoint().next().newBranch(1);
}
}
}
public void setContentDirty(boolean contentDirty)
{
this.isContentDirty = contentDirty;
}
public boolean isMetaDataDirty()
{
return this.isMetaDataDirty;
}
public void setMetaDataDirty(boolean metaDataDirty)
{
this.isMetaDataDirty = metaDataDirty;
}
public String getAttachmentURL(String filename, XWikiContext context)
{
return getAttachmentURL(filename, "download", context);
}
public String getAttachmentURL(String filename, String action, XWikiContext context)
{
return getAttachmentURL(filename, action, null, context);
}
public String getExternalAttachmentURL(String filename, String action, XWikiContext context)
{
URL url = context.getURLFactory().createAttachmentURL(filename, getSpace(), getName(), action, null,
getDatabase(), context);
return url.toString();
}
public String getAttachmentURL(String filename, String action, String querystring, XWikiContext context)
{
// Attachment file name cannot be empty
if (StringUtils.isEmpty(filename)) {
return null;
}
return context.getWiki().getAttachmentURL(new AttachmentReference(filename, this.getDocumentReference()),
action, querystring, context);
}
public String getAttachmentRevisionURL(String filename, String revision, XWikiContext context)
{
return getAttachmentRevisionURL(filename, revision, null, context);
}
public String getAttachmentRevisionURL(String filename, String revision, String querystring, XWikiContext context)
{
// Attachment file name cannot be empty
if (StringUtils.isEmpty(filename)) {
return null;
}
return context.getWiki().getAttachmentRevisionURL(new AttachmentReference(filename, getDocumentReference()),
revision, querystring, context);
}
/**
* @param action the action, see the {@code struts-config.xml} file for a list of all existing action names
* @param params the URL query string
* @param redirect true if the URL is going to be used in {@link HttpServletResponse#sendRedirect(String)}
* @param context the XWiki context
* @return the URL
*/
public String getURL(String action, String params, boolean redirect, XWikiContext context)
{
URL url =
context.getURLFactory().createURL(getSpace(), getName(), action, params, null, getDatabase(), context);
if (redirect && isRedirectAbsolute(context)) {
if (url == null) {
return null;
} else {
return url.toString();
}
} else {
return context.getURLFactory().getURL(url, context);
}
}
private boolean isRedirectAbsolute(XWikiContext context)
{
return StringUtils.equals("1", context.getWiki().Param("xwiki.redirect.absoluteurl"));
}
public String getURL(String action, boolean redirect, XWikiContext context)
{
return getURL(action, null, redirect, context);
}
public String getURL(String action, XWikiContext context)
{
return getURL(action, false, context);
}
public String getURL(String action, String querystring, XWikiContext context)
{
URL url =
context.getURLFactory().createURL(getSpace(), getName(), action, querystring, null, getDatabase(), context);
return context.getURLFactory().getURL(url, context);
}
public String getURL(String action, String querystring, String anchor, XWikiContext context)
{
URL url = context.getURLFactory().createURL(getSpace(), getName(), action, querystring, anchor, getDatabase(),
context);
return context.getURLFactory().getURL(url, context);
}
public String getExternalURL(String action, XWikiContext context)
{
URL url = context.getURLFactory().createExternalURL(getSpace(), getName(), action, null, null, getDatabase(),
context);
return url.toString();
}
public String getExternalURL(String action, String querystring, XWikiContext context)
{
URL url = context.getURLFactory().createExternalURL(getSpace(), getName(), action, querystring, null,
getDatabase(), context);
return url.toString();
}
public String getParentURL(XWikiContext context) throws XWikiException
{
XWikiDocument doc = new XWikiDocument(getParentReference());
URL url = context.getURLFactory().createURL(doc.getSpace(), doc.getName(), "view", null, null, getDatabase(),
context);
return context.getURLFactory().getURL(url, context);
}
public XWikiDocumentArchive getDocumentArchive(XWikiContext context) throws XWikiException
{
loadArchive(context);
return getDocumentArchive();
}
/**
* Create a new protected {@link com.xpn.xwiki.api.Document} public API to access page information and actions from
* scripting.
*
* @param customClassName the name of the custom {@link com.xpn.xwiki.api.Document} class of the object to create.
* @param context the XWiki context.
* @return a wrapped version of an XWikiDocument. Prefer this function instead of new Document(XWikiDocument,
* XWikiContext)
*/
public com.xpn.xwiki.api.Document newDocument(String customClassName, XWikiContext context)
{
if (!((customClassName == null) || (customClassName.equals("")))) {
try {
return newDocument(Class.forName(customClassName), context);
} catch (ClassNotFoundException e) {
LOGGER.error("Failed to get java Class object from class name", e);
}
}
return new com.xpn.xwiki.api.Document(this, context);
}
/**
* Create a new protected {@link com.xpn.xwiki.api.Document} public API to access page information and actions from
* scripting.
*
* @param customClass the custom {@link com.xpn.xwiki.api.Document} class the object to create.
* @param context the XWiki context.
* @return a wrapped version of an XWikiDocument. Prefer this function instead of new Document(XWikiDocument,
* XWikiContext)
*/
public com.xpn.xwiki.api.Document newDocument(Class<?> customClass, XWikiContext context)
{
if (customClass != null) {
try {
Class<?>[] classes = new Class[] { XWikiDocument.class, XWikiContext.class };
Object[] args = new Object[] { this, context };
return (com.xpn.xwiki.api.Document) customClass.getConstructor(classes).newInstance(args);
} catch (Exception e) {
LOGGER.error("Failed to create a custom Document object", e);
}
}
return new com.xpn.xwiki.api.Document(this, context);
}
public com.xpn.xwiki.api.Document newDocument(XWikiContext context)
{
String customClass = getCustomClass();
return newDocument(customClass, context);
}
public void loadArchive(XWikiContext context) throws XWikiException
{
if (this.archive == null || this.archive.get() == null) {
XWikiDocumentArchive arch = getVersioningStore(context).getXWikiDocumentArchive(this, context);
// We are using a SoftReference which will allow the archive to be
// discarded by the Garbage collector as long as the context is closed (usually during
// the request)
this.archive = new SoftReference<XWikiDocumentArchive>(arch);
}
}
/**
* @return the {@link XWikiDocumentArchive} for this document. If it is not stored in the document, null is
* returned.
*/
public XWikiDocumentArchive getDocumentArchive()
{
// If there is a soft reference, return it.
if (this.archive != null) {
return this.archive.get();
}
return null;
}
/**
* @return the {@link XWikiDocumentArchive} for this document. If it is not stored in the document, we get it using
* the current context. If there is an exception, null is returned.
*/
public XWikiDocumentArchive loadDocumentArchive()
{
XWikiDocumentArchive arch = getDocumentArchive();
if (arch != null) {
return arch;
}
XWikiContext xcontext = getXWikiContext();
try {
arch = getVersioningStore(xcontext).getXWikiDocumentArchive(this, xcontext);
// Put a copy of the archive in the soft reference for later use if needed.
setDocumentArchive(arch);
return arch;
} catch (Exception e) {
// VersioningStore.getXWikiDocumentArchive may throw an XWikiException, and xcontext or VersioningStore
// may be null (tests)
// To maintain the behavior of this method we can't throw an exception.
// Formerly, null was returned if there was no SoftReference.
LOGGER.warn("Could not get document archive", e);
return null;
}
}
public void setDocumentArchive(XWikiDocumentArchive arch)
{
// We are using a SoftReference which will allow the archive to be
// discarded by the Garbage collector as long as the context is closed (usually during the
// request)
if (arch != null) {
this.archive = new SoftReference<XWikiDocumentArchive>(arch);
}
}
public void setDocumentArchive(String sarch) throws XWikiException
{
XWikiDocumentArchive xda = new XWikiDocumentArchive(getId());
xda.setArchive(sarch);
setDocumentArchive(xda);
}
public Version[] getRevisions(XWikiContext context) throws XWikiException
{
return getVersioningStore(context).getXWikiDocVersions(this, context);
}
public String[] getRecentRevisions(int nb, XWikiContext context) throws XWikiException
{
try {
Version[] revisions = getVersioningStore(context).getXWikiDocVersions(this, context);
int length = nb;
// 0 means all revisions
if (nb == 0) {
length = revisions.length;
}
if (revisions.length < length) {
length = revisions.length;
}
String[] recentrevs = new String[length];
for (int i = 1; i <= length; i++) {
recentrevs[i - 1] = revisions[revisions.length - i].toString();
}
return recentrevs;
} catch (Exception e) {
return new String[0];
}
}
/**
* Get document versions matching criterias like author, minimum creation date, etc.
*
* @param criteria criteria used to match versions
* @return a list of matching versions
*/
public List<String> getRevisions(RevisionCriteria criteria, XWikiContext context) throws XWikiException
{
List<String> results = new ArrayList<String>();
Version[] revisions = getRevisions(context);
XWikiRCSNodeInfo nextNodeinfo = null;
XWikiRCSNodeInfo nodeinfo;
for (Version revision : revisions) {
nodeinfo = nextNodeinfo;
nextNodeinfo = getRevisionInfo(revision.toString(), context);
if (nodeinfo == null) {
continue;
}
// Minor/Major version matching
if (criteria.getIncludeMinorVersions() || !nextNodeinfo.isMinorEdit()) {
// Author matching
if (criteria.getAuthor().equals("") || criteria.getAuthor().equals(nodeinfo.getAuthor())) {
// Date range matching
Date versionDate = nodeinfo.getDate();
if (versionDate.after(criteria.getMinDate()) && versionDate.before(criteria.getMaxDate())) {
results.add(nodeinfo.getVersion().toString());
}
}
}
}
nodeinfo = nextNodeinfo;
if (nodeinfo != null) {
if (criteria.getAuthor().equals("") || criteria.getAuthor().equals(nodeinfo.getAuthor())) {
// Date range matching
Date versionDate = nodeinfo.getDate();
if (versionDate.after(criteria.getMinDate()) && versionDate.before(criteria.getMaxDate())) {
results.add(nodeinfo.getVersion().toString());
}
}
}
return criteria.getRange().subList(results);
}
public XWikiRCSNodeInfo getRevisionInfo(String version, XWikiContext context) throws XWikiException
{
return getDocumentArchive(context).getNode(new Version(version));
}
/**
* @return Is this version the most recent one. False if and only if there are newer versions of this document in
* the database.
*/
public boolean isMostRecent()
{
return this.mostRecent;
}
/**
* must not be used unless in store system.
*
* @param mostRecent - mark document as most recent.
*/
public void setMostRecent(boolean mostRecent)
{
this.mostRecent = mostRecent;
}
/**
* @since 2.2M1
*/
public BaseClass getXClass()
{
if (this.xClass == null) {
BaseClass emptyClass = new BaseClass();
// Make sure not to cause any false document versions if this document is saved.
emptyClass.setDirty(false);
this.setXClass(emptyClass);
}
return this.xClass;
}
/**
* @since 2.2M1
*/
public void setXClass(BaseClass xwikiClass)
{
xwikiClass.setOwnerDocument(this);
this.xClass = xwikiClass;
}
/**
* @since 2.2M1
*/
public Map<DocumentReference, List<BaseObject>> getXObjects()
{
return this.xObjects;
}
/**
* @since 2.2M1
*/
public void setXObjects(Map<DocumentReference, List<BaseObject>> objects)
{
if (objects == null) {
// Make sure we don`t set a null objects map since we assume everywhere that it is not null when using it.
objects = new HashMap<>();
}
boolean isDirty = false;
for (List<BaseObject> objList : objects.values()) {
for (BaseObject obj : objList) {
obj.setOwnerDocument(this);
isDirty = true;
}
}
// This operation resulted in marking the current document dirty.
if (isDirty) {
setMetaDataDirty(true);
}
// Replace the current objects with the provided ones.
this.xObjects = objects;
}
/**
* @since 2.2M1
*/
public BaseObject getXObject()
{
return getXObject(getDocumentReference());
}
/**
* @deprecated since 2.2M1 use {@link #getXObject()} instead
*/
@Deprecated
public BaseObject getxWikiObject()
{
return getXObject(getDocumentReference());
}
/**
* @since 2.2M1
*/
public List<BaseClass> getXClasses(XWikiContext context)
{
List<BaseClass> list = new ArrayList<BaseClass>();
// getXObjects() is a TreeMap, with elements sorted by className reference
for (DocumentReference classReference : getXObjects().keySet()) {
BaseClass bclass = null;
List<BaseObject> objects = getXObjects(classReference);
for (BaseObject obj : objects) {
if (obj != null) {
bclass = obj.getXClass(context);
if (bclass != null) {
break;
}
}
}
if (bclass != null) {
list.add(bclass);
}
}
return list;
}
/**
* Create and add a new object to the document with the provided class.
* <p>
* Note that absolute reference are not supported for xclasses which mean that the wiki part (whatever the wiki is)
* of the reference will be systematically removed.
*
* @param classReference the reference of the class
* @param context the XWiki context
* @return the index of teh newly created object
* @throws XWikiException error when creating the new object
* @since 2.2.3
*/
public int createXObject(EntityReference classReference, XWikiContext context) throws XWikiException
{
DocumentReference absoluteClassReference = resolveClassReference(classReference);
BaseObject object = BaseClass.newCustomClassInstance(absoluteClassReference, context);
object.setOwnerDocument(this);
object.setXClassReference(classReference);
List<BaseObject> objects = this.xObjects.get(absoluteClassReference);
if (objects == null) {
objects = new ArrayList<BaseObject>();
this.xObjects.put(absoluteClassReference, objects);
}
objects.add(object);
int nb = objects.size() - 1;
object.setNumber(nb);
setMetaDataDirty(true);
return nb;
}
/**
* @deprecated since 2.2M1 use {@link #createXObject(EntityReference, XWikiContext)} instead
*/
@Deprecated
public int createNewObject(String className, XWikiContext context) throws XWikiException
{
return createXObject(
getXClassEntityReferenceResolver().resolve(className, EntityType.DOCUMENT, getDocumentReference()),
context);
}
/**
* @since 2.2M1
*/
public int getXObjectSize(DocumentReference classReference)
{
try {
return getXObjects().get(classReference).size();
} catch (Exception e) {
return 0;
}
}
/**
* @since 7.3M1
* @since 7.2.1
* @since 7.1.3
* @since 6.4.6
*/
public int getXObjectSize(EntityReference classReference)
{
return getXObjectSize(resolveClassReference(classReference));
}
/**
* @deprecated since 2.2M1 use {@link #getXObjectSize(DocumentReference)} instead
*/
@Deprecated
public int getObjectNumbers(String className)
{
return getXObjectSize(resolveClassReference(className));
}
/**
* @since 2.2M1
*/
public List<BaseObject> getXObjects(DocumentReference classReference)
{
if (classReference == null) {
return new ArrayList<BaseObject>();
}
return getXObjects().get(classReference);
}
/**
* @since 3.3M1
*/
public List<BaseObject> getXObjects(EntityReference reference)
{
if (reference.getType() == EntityType.DOCUMENT) {
// class reference
return getXObjects(
getCurrentReferenceDocumentReferenceResolver().resolve(reference, getDocumentReference()));
}
return Collections.emptyList();
}
/**
* @deprecated since 2.2M1 use {@link #getXObjects(DocumentReference)} instead
*/
@Deprecated
public Vector<BaseObject> getObjects(String className)
{
List<BaseObject> result = this.xObjects.get(resolveClassReference(className));
return result == null ? null : new Vector<BaseObject>(result);
}
/**
* @since 2.2M1
*/
public void setXObjects(DocumentReference classReference, List<BaseObject> objects)
{
// Remove existing objects
List<BaseObject> existingbjects = this.xObjects.get(classReference);
if (existingbjects != null) {
existingbjects.clear();
}
for (BaseObject obj : objects) {
obj.setOwnerDocument(this);
}
// Add new objects
if (objects.isEmpty()) {
// Pretty wrong but can't remove that for retro compatibility reasons...
// Note that it means that someone can put an unmodifiable list here make impossible to add any object of
// this class.
this.xObjects.put(classReference, objects);
} else {
for (BaseObject baseObject : objects) {
addXObject(classReference, baseObject);
}
}
setMetaDataDirty(true);
}
/**
* @since 3.3M1
*/
public BaseObject getXObject(EntityReference reference)
{
if (reference instanceof DocumentReference) {
return getXObject((DocumentReference) reference);
} else if (reference.getType() == EntityType.DOCUMENT) {
// class reference
return getXObject(
getCurrentReferenceDocumentReferenceResolver().resolve(reference, getDocumentReference()));
} else if (reference.getType() == EntityType.OBJECT) {
// object reference
return getXObject(getCurrentReferenceObjectReferenceResolver().resolve(reference, getDocumentReference()));
}
return null;
}
/**
* @since 2.2M1
*/
public BaseObject getXObject(DocumentReference classReference)
{
BaseObject result = null;
List<BaseObject> objects = getXObjects().get(classReference);
if (objects != null) {
for (BaseObject object : objects) {
if (object != null) {
result = object;
break;
}
}
}
return result;
}
/**
* Get an object of this document based on its reference.
*
* @param objectReference the reference of the object
* @return the XWiki object
* @since 3.2M1
*/
public BaseObject getXObject(ObjectReference objectReference)
{
BaseObjectReference baseObjectReference;
if (objectReference instanceof BaseObjectReference) {
baseObjectReference = (BaseObjectReference) objectReference;
} else {
baseObjectReference = new BaseObjectReference(objectReference);
}
// If the baseObjectReference has an object number, we return the object with this number,
// otherwise, we consider it should be the first object, as specified by BaseObjectReference#getObjectNumber
return baseObjectReference.getObjectNumber() == null ? this.getXObject(baseObjectReference.getXClassReference())
: getXObject(baseObjectReference.getXClassReference(), baseObjectReference.getObjectNumber());
}
/**
* Get an object property of this document based on its reference.
*
* @param objectPropertyReference the reference of the object property
* @return the object property
* @since 3.2M3
*/
public BaseProperty<ObjectPropertyReference> getXObjectProperty(ObjectPropertyReference objectPropertyReference)
{
BaseObject object = getXObject((ObjectReference) objectPropertyReference.getParent());
if (object != null) {
return (BaseProperty<ObjectPropertyReference>) object.getField(objectPropertyReference.getName());
}
return null;
}
/**
* @deprecated since 2.2M1 use {@link #getXObject(DocumentReference)} instead
*/
@Deprecated
public BaseObject getObject(String className)
{
return getXObject(resolveClassReference(className));
}
/**
* @since 2.2M1
*/
public BaseObject getXObject(DocumentReference classReference, int nb)
{
List<BaseObject> objects = getXObjects().get(classReference);
if (objects != null && objects.size() > nb) {
return objects.get(nb);
}
return null;
}
/**
* Get an xobject with the passed xclass at the passed location.
* <p>
* If <code>create</code> is true and the is no xobject at the passed located, it's created.
*
* @param classReference the xlcass of the object to retrieve
* @param number the location of the xobject
* @param create if true the xobject is created when it does not exist
* @param xcontext the XWiki context
* @return a {@link BaseObject} stored at passed location
* @throws XWikiException when failing to create new xobject instance
* @since 7.3M1
* @since 7.2.1
* @since 7.1.3
* @since 6.4.6
*/
public BaseObject getXObject(EntityReference classReference, int number, boolean create, XWikiContext xcontext)
throws XWikiException
{
DocumentReference absoluteClassReference = resolveClassReference(classReference);
BaseObject xobject = getXObject(absoluteClassReference);
if (xobject == null && create) {
xobject = BaseClass.newCustomClassInstance(absoluteClassReference, xcontext);
setXObject(number, xobject);
}
return xobject;
}
/**
* @since 4.1M1
*/
public BaseObject getXObject(EntityReference classReference, int nb)
{
return getXObject(
getCurrentReferenceDocumentReferenceResolver().resolve(classReference, getDocumentReference()), nb);
}
/**
* @deprecated since 2.2M1 use {@link #getXObject(DocumentReference, int)} instead
*/
@Deprecated
public BaseObject getObject(String className, int nb)
{
return getXObject(resolveClassReference(className), nb);
}
/**
* @since 2.2M1
*/
public BaseObject getXObject(DocumentReference classReference, String key, String value)
{
return getXObject(classReference, key, value, false);
}
/**
* @deprecated since 2.2M1 use {@link #getXObject(DocumentReference, String, String)} instead
*/
@Deprecated
public BaseObject getObject(String className, String key, String value)
{
return getObject(className, key, value, false);
}
/**
* @return 6.3M1
*/
public BaseObject getXObject(EntityReference reference, String key, String value, boolean failover)
{
if (reference instanceof DocumentReference) {
return getXObject((DocumentReference) reference, key, value, failover);
} else if (reference.getType() == EntityType.DOCUMENT) {
// class reference
return getXObject(getCurrentReferenceDocumentReferenceResolver().resolve(reference, getDocumentReference()),
key, value, failover);
}
return null;
}
/**
* @since 2.2M1
*/
public BaseObject getXObject(DocumentReference classReference, String key, String value, boolean failover)
{
try {
if (value == null) {
if (failover) {
return getXObject(classReference);
} else {
return null;
}
}
List<BaseObject> objects = getXObjects().get(classReference);
if ((objects == null) || (objects.size() == 0)) {
return null;
}
for (BaseObject obj : objects) {
if (obj != null) {
if (value.equals(obj.getStringValue(key))) {
return obj;
}
}
}
if (failover) {
return getXObject(classReference);
} else {
return null;
}
} catch (Exception e) {
if (failover) {
return getXObject(classReference);
}
LOGGER.warn("Exception while accessing objects for document [{}]: {}", getDocumentReference(),
e.getMessage(), e);
return null;
}
}
/**
* @deprecated since 2.2M1 use {@link #getXObject(DocumentReference, String, String, boolean)} instead
*/
@Deprecated
public BaseObject getObject(String className, String key, String value, boolean failover)
{
return getXObject(resolveClassReference(className), key, value, failover);
}
/**
* @since 2.2M1
* @deprecated use {@link #addXObject(BaseObject)} instead
*/
@Deprecated
public void addXObject(DocumentReference classReference, BaseObject object)
{
List<BaseObject> vobj = this.xObjects.get(classReference);
if (vobj == null) {
setXObject(classReference, 0, object);
} else {
setXObject(classReference, vobj.size(), object);
}
}
/**
* Add the object to the document.
*
* @param object the xobject to add
* @throws NullPointerException if the specified object is null because we need the get the class reference from the
* object
* @since 2.2.3
*/
public void addXObject(BaseObject object)
{
object.setOwnerDocument(this);
List<BaseObject> vobj = this.xObjects.get(object.getXClassReference());
if (vobj == null) {
setXObject(0, object);
} else {
setXObject(vobj.size(), object);
}
}
/**
* @deprecated since 2.2M1 use {@link #addXObject(BaseObject)} instead
*/
@Deprecated
public void addObject(String className, BaseObject object)
{
addXObject(resolveClassReference(className), object);
}
/**
* @since 2.2M1
* @deprecated use {@link #setXObject(int, BaseObject)} instead
*/
@Deprecated
public void setXObject(DocumentReference classReference, int nb, BaseObject object)
{
if (object != null) {
object.setOwnerDocument(this);
object.setNumber(nb);
}
List<BaseObject> objects = this.xObjects.get(classReference);
if (objects == null) {
objects = new ArrayList<BaseObject>();
this.xObjects.put(classReference, objects);
}
while (nb >= objects.size()) {
objects.add(null);
}
objects.set(nb, object);
setMetaDataDirty(true);
}
/**
* Replaces the object at the specified position and for the specified object's xclass.
*
* @param nb index of the element to replace
* @param object the xobject to insert
* @throws NullPointerException if the specified object is null because we need the get the class reference from the
* object
* @since 2.2.3
*/
public void setXObject(int nb, BaseObject object)
{
object.setOwnerDocument(this);
object.setNumber(nb);
List<BaseObject> objects = this.xObjects.get(object.getXClassReference());
if (objects == null) {
objects = new ArrayList<BaseObject>();
this.xObjects.put(object.getXClassReference(), objects);
}
while (nb >= objects.size()) {
objects.add(null);
}
objects.set(nb, object);
setMetaDataDirty(true);
}
/**
* @deprecated since 2.2M1 use {@link #setXObject(DocumentReference, int, BaseObject)} instead
*/
@Deprecated
public void setObject(String className, int nb, BaseObject object)
{
setXObject(resolveClassReference(className), nb, object);
}
/**
* @return true if the document is a new one (i.e. it has never been saved) or false otherwise
*/
public boolean isNew()
{
return this.isNew;
}
public void setNew(boolean aNew)
{
this.isNew = aNew;
}
/**
* @since 2.2M1
*/
public void mergeXClass(XWikiDocument templatedoc)
{
BaseClass bclass = getXClass();
BaseClass tbclass = templatedoc.getXClass();
if (tbclass != null) {
if (bclass == null) {
setXClass(tbclass.clone());
} else {
getXClass().merge(tbclass.clone());
}
}
setMetaDataDirty(true);
}
/**
* @deprecated since 2.2M1 use {@link #mergeXClass(XWikiDocument)} instead
*/
@Deprecated
public void mergexWikiClass(XWikiDocument templatedoc)
{
mergeXClass(templatedoc);
}
/**
* @since 2.2M1
*/
public void mergeXObjects(XWikiDocument templateDoc)
{
for (Map.Entry<DocumentReference, List<BaseObject>> entry : templateDoc.getXObjects().entrySet()) {
// Documents can't have objects of types defined in a different wiki so we make sure the class reference
// matches this document's wiki.
DocumentReference classReference = entry.getKey().replaceParent(entry.getKey().getWikiReference(),
getDocumentReference().getWikiReference());
// Copy the objects from the template document only if this document doesn't have them already.
//
// Note: this might be a bit misleading since it will not add objects from the template if some objects of
// that class already exist in the current document.
if (getXObjectSize(classReference) == 0) {
for (BaseObject object : entry.getValue()) {
if (object != null) {
addXObject(object.duplicate());
}
}
}
}
}
/**
* @deprecated since 2.2M1 use {@link #mergeXObjects(XWikiDocument)} instead
*/
@Deprecated
public void mergexWikiObjects(XWikiDocument templatedoc)
{
mergeXObjects(templatedoc);
}
/**
* @since 2.2M1
*/
public void cloneXObjects(XWikiDocument templatedoc)
{
cloneXObjects(templatedoc, true);
}
/**
* @since 2.2.3
*/
public void duplicateXObjects(XWikiDocument templatedoc)
{
cloneXObjects(templatedoc, false);
}
/**
* Copy specified document objects into current document.
*
* @param templatedoc the document to copy
* @param keepsIdentity if true it does an exact java copy, otherwise it duplicate objects with the new document
* name (and new class names)
*/
private void cloneXObjects(XWikiDocument templatedoc, boolean keepsIdentity)
{
// clean map
this.xObjects.clear();
// fill map
for (Map.Entry<DocumentReference, List<BaseObject>> entry : templatedoc.getXObjects().entrySet()) {
List<BaseObject> tobjects = entry.getValue();
// clone and insert xobjects
for (BaseObject otherObject : tobjects) {
if (otherObject != null) {
if (keepsIdentity) {
addXObject(otherObject.clone());
} else {
BaseObject newObject = otherObject.duplicate(getDocumentReference());
setXObject(newObject.getNumber(), newObject);
}
} else if (keepsIdentity) {
// set null object to make sure to have exactly the same thing when cloning a document
addXObject(entry.getKey(), null);
}
}
}
}
/**
* @since 2.2M1
*/
public DocumentReference getTemplateDocumentReference()
{
return this.templateDocumentReference;
}
/**
* @deprecated since 2.2M1 use {@link #getTemplateDocumentReference()} instead
*/
@Deprecated
public String getTemplate()
{
String templateReferenceAsString;
DocumentReference templateDocumentReference = getTemplateDocumentReference();
if (templateDocumentReference != null) {
templateReferenceAsString = LOCAL_REFERENCE_SERIALIZER.serialize(templateDocumentReference);
} else {
templateReferenceAsString = "";
}
return templateReferenceAsString;
}
/**
* @since 2.2M1
*/
public void setTemplateDocumentReference(DocumentReference templateDocumentReference)
{
if (!Objects.equals(getTemplateDocumentReference(), templateDocumentReference)) {
this.templateDocumentReference = templateDocumentReference;
setMetaDataDirty(true);
}
}
/**
* @deprecated since 2.2M1 use {@link #setTemplateDocumentReference(DocumentReference)} instead
*/
@Deprecated
public void setTemplate(String template)
{
DocumentReference templateReference = null;
if (!StringUtils.isEmpty(template)) {
templateReference = getCurrentMixedDocumentReferenceResolver().resolve(template);
}
setTemplateDocumentReference(templateReference);
}
public String displayPrettyName(String fieldname, XWikiContext context)
{
return displayPrettyName(fieldname, false, true, context);
}
public String displayPrettyName(String fieldname, boolean showMandatory, XWikiContext context)
{
return displayPrettyName(fieldname, showMandatory, true, context);
}
public String displayPrettyName(String fieldname, boolean showMandatory, boolean before, XWikiContext context)
{
try {
BaseObject object = getXObject();
if (object == null) {
object = getFirstObject(fieldname, context);
}
return displayPrettyName(fieldname, showMandatory, before, object, context);
} catch (Exception e) {
return "";
}
}
public String displayPrettyName(String fieldname, BaseObject obj, XWikiContext context)
{
return displayPrettyName(fieldname, false, true, obj, context);
}
public String displayPrettyName(String fieldname, boolean showMandatory, BaseObject obj, XWikiContext context)
{
return displayPrettyName(fieldname, showMandatory, true, obj, context);
}
public String displayPrettyName(String fieldname, boolean showMandatory, boolean before, BaseObject obj,
XWikiContext context)
{
try {
PropertyClass pclass = (PropertyClass) obj.getXClass(context).get(fieldname);
String dprettyName = "";
if (showMandatory) {
dprettyName = context.getWiki().addMandatory(context);
}
if (before) {
return dprettyName + pclass.getPrettyName(context);
} else {
return pclass.getPrettyName(context) + dprettyName;
}
} catch (Exception e) {
return "";
}
}
public String displayTooltip(String fieldname, XWikiContext context)
{
try {
BaseObject object = getXObject();
if (object == null) {
object = getFirstObject(fieldname, context);
}
return displayTooltip(fieldname, object, context);
} catch (Exception e) {
return "";
}
}
public String displayTooltip(String fieldname, BaseObject obj, XWikiContext context)
{
String result = "";
try {
PropertyClass pclass = (PropertyClass) obj.getXClass(context).get(fieldname);
String tooltip = pclass.getTooltip(context);
if ((tooltip != null) && (!tooltip.trim().equals(""))) {
String img = "<img src=\"" + context.getWiki().getSkinFile("info.gif", context)
+ "\" class=\"tooltip_image\" align=\"middle\" />";
result = context.getWiki().addTooltip(img, tooltip, context);
}
} catch (Exception e) {
}
return result;
}
/**
* @param fieldname the name of the field to display
* @param context the XWiki context
* @return the rendered field
*/
public String display(String fieldname, XWikiContext context)
{
String result = "";
try {
BaseObject object = getXObject();
if (object == null) {
object = getFirstObject(fieldname, context);
}
result = display(fieldname, object, context);
} catch (Exception e) {
LOGGER.error("Failed to display field [" + fieldname + "] of document ["
+ getDefaultEntityReferenceSerializer().serialize(getDocumentReference()) + "]", e);
}
return result;
}
/**
* @param fieldname the name of the field to display
* @param obj the object containing the field to display
* @param context the XWiki context
* @return the rendered field
*/
public String display(String fieldname, BaseObject obj, XWikiContext context)
{
String type = null;
try {
type = (String) context.get("display");
} catch (Exception e) {
}
if (type == null) {
type = "view";
}
return display(fieldname, type, obj, context);
}
/**
* @param fieldname the name of the field to display
* @param mode the mode to use ("view", "edit", ...)
* @param context the XWiki context
* @return the rendered field
*/
public String display(String fieldname, String mode, XWikiContext context)
{
return display(fieldname, mode, "", context);
}
/**
* @param fieldname the name of the field to display
* @param type the type of the field to display
* @param obj the object containing the field to display
* @param context the XWiki context
* @return the rendered field
*/
public String display(String fieldname, String type, BaseObject obj, XWikiContext context)
{
return display(fieldname, type, "", obj, context);
}
/**
* @param fieldname the name of the field to display
* @param mode the mode to use ("view", "edit", ...)
* @param prefix the prefix to add in the field identifier in edit display for example
* @param context the XWiki context
* @return the rendered field
*/
public String display(String fieldname, String mode, String prefix, XWikiContext context)
{
try {
BaseObject object = getXObject();
if (object == null) {
object = getFirstObject(fieldname, context);
}
if (object == null) {
return "";
} else {
return display(fieldname, mode, prefix, object, context);
}
} catch (Exception e) {
return "";
}
}
/**
* @param fieldname the name of the field to display
* @param type the type of the field to display
* @param obj the object containing the field to display
* @param wrappingSyntaxId the syntax of the content in which the result will be included. This to take care of some
* escaping depending of the syntax.
* @param context the XWiki context
* @return the rendered field
*/
public String display(String fieldname, String type, BaseObject obj, String wrappingSyntaxId, XWikiContext context)
{
return display(fieldname, type, "", obj, wrappingSyntaxId, context);
}
/**
* @param fieldname the name of the field to display
* @param type the type of the field to display
* @param pref the prefix to add in the field identifier in edit display for example
* @param obj the object containing the field to display
* @param context the XWiki context
* @return the rendered field
*/
public String display(String fieldname, String type, String pref, BaseObject obj, XWikiContext context)
{
return display(fieldname, type, pref, obj, context.getWiki().getCurrentContentSyntaxId(getSyntaxId(), context),
context);
}
/**
* @param fieldname the name of the field to display
* @param type the type of the field to display
* @param pref the prefix to add in the field identifier in edit display for example
* @param obj the object containing the field to display
* @param wrappingSyntaxId the syntax of the content in which the result will be included. This to take care of some
* escaping depending of the syntax.
* @param context the XWiki context
* @return the rendered field
*/
public String display(String fieldname, String type, String pref, BaseObject obj, String wrappingSyntaxId,
XWikiContext context)
{
if (obj == null) {
return "";
}
boolean isInRenderingEngine = BooleanUtils.toBoolean((Boolean) context.get("isInRenderingEngine"));
HashMap<String, Object> backup = new HashMap<String, Object>();
try {
backupContext(backup, context);
setAsContextDoc(context);
// Make sure to execute with the right of the document author instead of the content author
// (because modifying object property does not modify content author)
XWikiDocument sdoc = context.getDoc();
if (sdoc != null && !Objects.equals(sdoc.getContentAuthorReference(), sdoc.getAuthorReference())) {
// Hack the sdoc to make test module believe the content author is the author
sdoc = sdoc.clone();
sdoc.setContentAuthorReference(sdoc.getAuthorReference());
context.put(CKEY_SDOC, sdoc);
}
type = type.toLowerCase();
StringBuffer result = new StringBuffer();
PropertyClass pclass = (PropertyClass) obj.getXClass(context).get(fieldname);
String prefix = pref + LOCAL_REFERENCE_SERIALIZER.serialize(obj.getXClass(context).getDocumentReference())
+ "_" + obj.getNumber() + "_";
if (pclass == null) {
return "";
} else if (pclass.isCustomDisplayed(context)) {
pclass.displayCustom(result, fieldname, prefix, type, obj, context);
} else if (type.equals("view")) {
pclass.displayView(result, fieldname, prefix, obj, context);
} else if (type.equals("rendered")) {
String fcontent = pclass.displayView(fieldname, prefix, obj, context);
// This mode is deprecated for the new rendering and should also be removed for the old rendering
// since the way to implement this now is to choose the type of rendering to do in the class itself.
// Thus for the new rendering we simply make this mode work like the "view" mode.
if (is10Syntax(wrappingSyntaxId)) {
result.append(getRenderedContent(fcontent, getSyntaxId(), context));
} else {
result.append(fcontent);
}
} else if (type.equals("edit")) {
context.addDisplayedField(fieldname);
// If the Syntax id is "xwiki/1.0" then use the old rendering subsystem and prevent wiki syntax
// rendering using the pre macro. In the new rendering system it's the XWiki Class itself that does the
// escaping. For example for a textarea check the TextAreaClass class.
if (is10Syntax(wrappingSyntaxId)) {
// Don't use pre when not in the rendernig engine since for template we don't evaluate wiki syntax.
if (isInRenderingEngine) {
result.append("{pre}");
}
}
pclass.displayEdit(result, fieldname, prefix, obj, context);
if (is10Syntax(wrappingSyntaxId)) {
if (isInRenderingEngine) {
result.append("{/pre}");
}
}
} else if (type.equals("hidden")) {
// If the Syntax id is "xwiki/1.0" then use the old rendering subsystem and prevent wiki syntax
// rendering using the pre macro. In the new rendering system it's the XWiki Class itself that does the
// escaping. For example for a textarea check the TextAreaClass class.
if (is10Syntax(wrappingSyntaxId) && isInRenderingEngine) {
result.append("{pre}");
}
pclass.displayHidden(result, fieldname, prefix, obj, context);
if (is10Syntax(wrappingSyntaxId) && isInRenderingEngine) {
result.append("{/pre}");
}
} else if (type.equals("search")) {
// Backward compatibility
// Check if the method has been injected using aspects
Method searchMethod = null;
for (Method method : pclass.getClass().getMethods()) {
if (method.getName().equals("displaySearch") && method.getParameterTypes().length == 5) {
searchMethod = method;
break;
}
}
if (searchMethod != null) {
// If the Syntax id is "xwiki/1.0" then use the old rendering subsystem and prevent wiki syntax
// rendering using the pre macro. In the new rendering system it's the XWiki Class itself that does
// the
// escaping. For example for a textarea check the TextAreaClass class.
if (is10Syntax(wrappingSyntaxId) && isInRenderingEngine) {
result.append("{pre}");
}
prefix = LOCAL_REFERENCE_SERIALIZER.serialize(obj.getXClass(context).getDocumentReference()) + "_";
searchMethod.invoke(pclass, result, fieldname, prefix, context.get("query"), context);
if (is10Syntax(wrappingSyntaxId) && isInRenderingEngine) {
result.append("{/pre}");
}
} else {
pclass.displayView(result, fieldname, prefix, obj, context);
}
} else {
pclass.displayView(result, fieldname, prefix, obj, context);
}
// If we're in new rendering engine we want to wrap the HTML returned by displayView() in
// a {{html/}} macro so that the user doesn't have to do it.
// We test if we're inside the rendering engine since it's also possible that this display() method is
// called
// directly from a template and in this case we only want HTML as a result and not wiki syntax.
// TODO: find a more generic way to handle html macro because this works only for XWiki 1.0 and XWiki 2.0
// Add the {{html}}{{/html}} only when result really contains html since it's not needed for pure text
if (isInRenderingEngine && !is10Syntax(wrappingSyntaxId)
&& (result.indexOf("<") != -1 || result.indexOf(">") != -1)) {
result.insert(0, "{{html clean=\"false\" wiki=\"false\"}}");
result.append("{{/html}}");
}
return result.toString();
} catch (Exception ex) {
// TODO: It would better to check if the field exists rather than catching an exception
// raised by a NPE as this is currently the case here...
LOGGER.warn("Failed to display field [" + fieldname + "] in [" + type + "] mode for Object of Class ["
+ getDefaultEntityReferenceSerializer().serialize(obj.getDocumentReference()) + "]", ex);
return "";
} finally {
restoreContext(backup, context);
}
}
/**
* @since 2.2M1
*/
public String displayForm(DocumentReference classReference, String header, String format, XWikiContext context)
{
return displayForm(classReference, header, format, true, context);
}
/**
* @deprecated since 2.2M1, use {@link #displayForm(DocumentReference, String, String, XWikiContext)} instead
*/
@Deprecated
public String displayForm(String className, String header, String format, XWikiContext context)
{
return displayForm(className, header, format, true, context);
}
/**
* @since 2.2M1
*/
public String displayForm(DocumentReference classReference, String header, String format, boolean linebreak,
XWikiContext context)
{
List<BaseObject> objects = getXObjects(classReference);
if (format.endsWith("\\n")) {
linebreak = true;
}
BaseObject firstobject = null;
Iterator<BaseObject> foit = objects.iterator();
while ((firstobject == null) && foit.hasNext()) {
firstobject = foit.next();
}
if (firstobject == null) {
return "";
}
BaseClass bclass = firstobject.getXClass(context);
if (bclass.getPropertyList().size() == 0) {
return "";
}
StringBuilder result = new StringBuilder();
VelocityContext vcontext = new VelocityContext();
for (String propertyName : bclass.getPropertyList()) {
PropertyClass pclass = (PropertyClass) bclass.getField(propertyName);
vcontext.put(pclass.getName(), pclass.getPrettyName());
}
result.append(evaluate(header, context.getDoc().getPrefixedFullName(), vcontext, context));
if (linebreak) {
result.append("\n");
}
// display each line
for (int i = 0; i < objects.size(); i++) {
vcontext.put("id", Integer.valueOf(i + 1));
BaseObject object = objects.get(i);
if (object != null) {
for (String name : bclass.getPropertyList()) {
vcontext.put(name, display(name, object, context));
}
result.append(evaluate(format, context.getDoc().getPrefixedFullName(), vcontext, context));
if (linebreak) {
result.append("\n");
}
}
}
return result.toString();
}
private String evaluate(String content, String name, VelocityContext vcontext, XWikiContext context)
{
StringWriter writer = new StringWriter();
try {
VelocityManager velocityManager = Utils.getComponent(VelocityManager.class);
velocityManager.getVelocityEngine().evaluate(vcontext, writer, name, content);
return writer.toString();
} catch (Exception e) {
LOGGER.error("Error while parsing velocity template namespace [{}]", name, e);
Object[] args = { name };
XWikiException xe = new XWikiException(XWikiException.MODULE_XWIKI_RENDERING,
XWikiException.ERROR_XWIKI_RENDERING_VELOCITY_EXCEPTION, "Error while parsing velocity page {0}", e,
args);
return Util.getHTMLExceptionMessage(xe, context);
}
}
/**
* @deprecated since 2.2M1, use {@link #displayForm(DocumentReference, String, String, boolean, XWikiContext)}
* instead
*/
@Deprecated
public String displayForm(String className, String header, String format, boolean linebreak, XWikiContext context)
{
return displayForm(resolveClassReference(className), header, format, linebreak, context);
}
/**
* @since 2.2M1
*/
public String displayForm(DocumentReference classReference, XWikiContext context)
{
List<BaseObject> objects = getXObjects(classReference);
if (objects == null) {
return "";
}
BaseObject firstobject = null;
Iterator<BaseObject> foit = objects.iterator();
while ((firstobject == null) && foit.hasNext()) {
firstobject = foit.next();
}
if (firstobject == null) {
return "";
}
BaseClass bclass = firstobject.getXClass(context);
if (bclass.getPropertyList().size() == 0) {
return "";
}
StringBuilder result = new StringBuilder();
result.append("{table}\n");
boolean first = true;
for (String propertyName : bclass.getPropertyList()) {
if (first == true) {
first = false;
} else {
result.append("|");
}
PropertyClass pclass = (PropertyClass) bclass.getField(propertyName);
result.append(pclass.getPrettyName());
}
result.append("\n");
for (int i = 0; i < objects.size(); i++) {
BaseObject object = objects.get(i);
if (object != null) {
first = true;
for (String propertyName : bclass.getPropertyList()) {
if (first == true) {
first = false;
} else {
result.append("|");
}
String data = display(propertyName, object, context);
data = data.trim();
data = data.replaceAll("\n", " ");
if (data.length() == 0) {
result.append(" ");
} else {
result.append(data);
}
}
result.append("\n");
}
}
result.append("{table}\n");
return result.toString();
}
/**
* @deprecated since 2.2M1, use {@link #displayForm(DocumentReference, XWikiContext)} instead
*/
@Deprecated
public String displayForm(String className, XWikiContext context)
{
return displayForm(resolveClassReference(className), context);
}
public boolean isFromCache()
{
return this.fromCache;
}
public void setFromCache(boolean fromCache)
{
this.fromCache = fromCache;
}
public void readDocMetaFromForm(EditForm eform, XWikiContext context) throws XWikiException
{
String defaultLanguage = eform.getDefaultLanguage();
if (defaultLanguage != null) {
setDefaultLanguage(defaultLanguage);
}
String defaultTemplate = eform.getDefaultTemplate();
if (defaultTemplate != null) {
setDefaultTemplate(defaultTemplate);
}
String creator = eform.getCreator();
if ((creator != null) && (!creator.equals(getCreator()))) {
if ((getCreatorReference().equals(context.getUserReference()))
|| (context.getWiki().getRightService().hasAdminRights(context))) {
setCreator(creator);
}
}
String parent = eform.getParent();
if (parent != null) {
setParent(parent);
}
// Read the comment from the form
String comment = eform.getComment();
if (comment != null) {
setComment(comment);
}
// Read the minor edit checkbox from the form
setMinorEdit(eform.isMinorEdit());
String tags = eform.getTags();
if (!StringUtils.isEmpty(tags)) {
setTags(tags, context);
}
// Set the Syntax id if defined
String syntaxId = eform.getSyntaxId();
if (syntaxId != null) {
setSyntaxId(syntaxId);
}
// Read the hidden checkbox from the form
if (eform.getHidden() != null) {
setHidden("1".equals(eform.getHidden()));
}
}
/**
* add tags to the document.
*/
public void setTags(String tagsStr, XWikiContext context) throws XWikiException
{
BaseClass tagsClass = context.getWiki().getTagClass(context);
StaticListClass tagProp = (StaticListClass) tagsClass.getField(XWikiConstant.TAG_CLASS_PROP_TAGS);
BaseObject tags = getObject(XWikiConstant.TAG_CLASS, true, context);
tags.safeput(XWikiConstant.TAG_CLASS_PROP_TAGS, tagProp.fromString(tagsStr));
setMetaDataDirty(true);
}
public String getTags(XWikiContext context)
{
ListProperty prop = (ListProperty) getTagProperty(context);
// I don't know why we need to XML-escape the list of tags but for backwards compatibility we need to keep doing
// this. When this method was added it was using ListProperty#getTextValue() which used to return
// ListProperty#toFormString() before we fixed it to return the unescaped value because we need to save the raw
// value in the database and ListProperty#getTextValue() is called when the list property is saved.
return prop != null ? prop.toFormString() : "";
}
public List<String> getTagsList(XWikiContext context)
{
List<String> tagList = null;
BaseProperty prop = getTagProperty(context);
if (prop != null) {
tagList = (List<String>) prop.getValue();
}
return tagList;
}
private BaseProperty getTagProperty(XWikiContext context)
{
BaseObject tags = getObject(XWikiConstant.TAG_CLASS);
return tags != null ? ((BaseProperty) tags.safeget(XWikiConstant.TAG_CLASS_PROP_TAGS)) : null;
}
public List<String> getTagsPossibleValues(XWikiContext context)
{
List<String> list;
try {
BaseClass tagsClass = context.getWiki().getTagClass(context);
String possibleValues =
((StaticListClass) tagsClass.getField(XWikiConstant.TAG_CLASS_PROP_TAGS)).getValues();
return ListClass.getListFromString(possibleValues);
} catch (XWikiException e) {
LOGGER.error("Failed to get tag class", e);
list = Collections.emptyList();
}
return list;
}
public void readTranslationMetaFromForm(EditForm eform, XWikiContext context) throws XWikiException
{
String content = eform.getContent();
if (content != null) {
// Cleanup in case we use HTMLAREA
// content = context.getUtil().substitute("s/<br class=\\\"htmlarea\\\"\\/>/\\r\\n/g",
// content);
content = context.getUtil().substitute("s/<br class=\"htmlarea\" \\/>/\r\n/g", content);
setContent(content);
}
String title = eform.getTitle();
if (title != null) {
setTitle(title);
}
}
public void readObjectsFromForm(EditForm eform, XWikiContext context) throws XWikiException
{
for (DocumentReference reference : getXObjects().keySet()) {
List<BaseObject> oldObjects = getXObjects(reference);
List<BaseObject> newObjects = new ArrayList<BaseObject>();
while (newObjects.size() < oldObjects.size()) {
newObjects.add(null);
}
for (int i = 0; i < oldObjects.size(); i++) {
BaseObject oldobject = oldObjects.get(i);
if (oldobject != null) {
BaseClass baseclass = oldobject.getXClass(context);
BaseObject newobject = (BaseObject) baseclass.fromMap(
eform.getObject(
LOCAL_REFERENCE_SERIALIZER.serialize(baseclass.getDocumentReference()) + "_" + i),
oldobject);
newobject.setNumber(oldobject.getNumber());
newobject.setGuid(oldobject.getGuid());
newobject.setOwnerDocument(this);
newObjects.set(newobject.getNumber(), newobject);
}
}
getXObjects().put(reference, newObjects);
}
}
/**
* Generate a map from the request parameters of the form '<spacename>.<classname>_<number>_<propertyname>' Keys of
* this map will be the reference '<spacename>.<classname>' to the Class (for example, 'XWiki.XWikiRights'), the
* content is a list where each element describe property for the object <number>. Element of the list is a map
* where key is <propertyname> and content is the array of corresponding values. Example with a list of HTTP
* parameters:
* <ul>
* <li>XWiki.XWikiRights_0_users=XWiki.Admin</li>
* <li>XWiki.XWikiRights_0_users=XWiki.Me</li>
* <li>XWiki.XWikiRights_0_groups=XWiki.XWikiAllGroup</li>
* <li>XWiki.XWikiRights_1_user=XWiki.Admin</li>
* <li>XWiki.XWikiUsers_1_name=Spirou</li>
* </ul>
* will result in the following map <code><pre>
* {
* "XWiki.XWikiRights": {
* "0": {
* "users": ["XWiki.Admin", "XWiki.Me"],
* "groups": ["XWiki.XWikiAllGroup"]
* },
* "1": {
* "users": ["XWiki.Admin"]
* }
* ],
* "XWiki.XWikiUsers":
* "1": {
* "name": ["Spirou"]
* }
* ]
* }
* </pre></code>
*
* @param request is the input HTTP request that provides the parameters
* @param context
* @return a map containing ordered data
*/
private Map<DocumentReference, SortedMap<Integer, Map<String, String[]>>> parseRequestUpdateOrCreate(
XWikiRequest request, XWikiContext context)
{
Map<DocumentReference, SortedMap<Integer, Map<String, String[]>>> result = new HashMap<>();
@SuppressWarnings("unchecked")
Map<String, String[]> allParameters = request.getParameterMap();
for (Entry<String, String[]> parameter : allParameters.entrySet()) {
Matcher matcher = XPROPERTY_REFERENCE_PATTERN.matcher(parameter.getKey());
if (matcher.matches() == false) {
continue;
}
Integer classNumber;
String className = matcher.group(1);
String classNumberAsString = matcher.group(2);
String classPropertyName = matcher.group(3);
DocumentReference classReference = getCurrentDocumentReferenceResolver().resolve(className);
try {
BaseClass xClass = context.getWiki().getDocument(classReference, context).getXClass();
if (xClass.getPropertyList().contains(classPropertyName) == false) {
continue;
}
classNumber = Integer.parseInt(classNumberAsString);
} catch (XWikiException e) {
// If the class page cannot be found, skip the property update
LOGGER.warn("Failed to load document [{}], ignoring property update [{}]. Reason: [{}]", classReference,
parameter.getKey(), ExceptionUtils.getRootCauseMessage(e));
continue;
} catch (NumberFormatException e) {
// If the numner isn't valid, skip the property update
LOGGER.warn("Invalid xobject number [{}], ignoring property update [{}].", classNumberAsString,
parameter.getKey());
continue;
}
SortedMap<Integer, Map<String, String[]>> objectMap = result.get(classReference);
if (objectMap == null) {
objectMap = new TreeMap<>();
result.put(classReference, objectMap);
}
// Get the property from the right object #objectNumber of type 'objectName'; create it if they don't exist
Map<String, String[]> object = objectMap.get(classNumber);
if (object == null) {
object = new HashMap<>();
objectMap.put(classNumber, object);
}
object.put(classPropertyName, parameter.getValue());
}
return result;
}
/**
* Create and/or update objects in a document given a list of HTTP parameters of the form {@code
* <spacename>.<classname>_<number>_<propertyname>}. If the object already exists, the field is replace by the given
* value. If the object doesn't exist in the document, it is created then the property {@code <propertyname>} is
* initialized with the given value. An object is only created if the given {@code <number>} is 'one-more' than the
* existing number of objects. For example, if the document already has 2 objects of type {@code Space.Class}, then
* it will create a new object only with {@code Space.Class_2_prop=something}. Every other parameter like {@code
* Space.Class_42_prop=foobar} for example, will be ignore.
*
* @param eform is form information that contains all the query parameters
* @param context
* @throws XWikiException
* @since 7.1M1
*/
public void readObjectsFromFormUpdateOrCreate(EditForm eform, XWikiContext context) throws XWikiException
{
Map<DocumentReference, SortedMap<Integer, Map<String, String[]>>> fromRequest =
parseRequestUpdateOrCreate(eform.getRequest(), context);
for (Entry<DocumentReference, SortedMap<Integer, Map<String, String[]>>> requestClassEntries : fromRequest
.entrySet()) {
DocumentReference requestClassReference = requestClassEntries.getKey();
SortedMap<Integer, Map<String, String[]>> requestObjectMap = requestClassEntries.getValue();
for (Entry<Integer, Map<String, String[]>> requestObjectEntry : requestObjectMap.entrySet()) {
Integer requestObjectNumber = requestObjectEntry.getKey();
Map<String, String[]> requestObjectPropertyMap = requestObjectEntry.getValue();
BaseObject oldObject = getXObject(requestClassReference, requestObjectNumber);
if (oldObject == null) {
// Create the object only if it has been numbered one more than the number of existing objects
if (requestObjectPropertyMap != null) {
oldObject = newXObject(requestClassReference, context);
} else {
break;
}
}
BaseClass baseClass = oldObject.getXClass(context);
BaseObject newObject = (BaseObject) baseClass.fromMap(requestObjectPropertyMap, oldObject);
newObject.setNumber(oldObject.getNumber());
newObject.setGuid(oldObject.getGuid());
newObject.setOwnerDocument(this);
setXObject(requestObjectNumber, newObject);
}
}
}
public void readFromForm(EditForm eform, XWikiContext context) throws XWikiException
{
readDocMetaFromForm(eform, context);
readTranslationMetaFromForm(eform, context);
ObjectPolicyType objectPolicy = eform.getObjectPolicy();
if (objectPolicy == null || objectPolicy.equals(ObjectPolicyType.UPDATE)) {
readObjectsFromForm(eform, context);
} else if (objectPolicy.equals(ObjectPolicyType.UPDATE_OR_CREATE)) {
readObjectsFromFormUpdateOrCreate(eform, context);
}
}
public void readFromTemplate(EditForm eform, XWikiContext context) throws XWikiException
{
String template = eform.getTemplate();
readFromTemplate(template, context);
}
/**
* @since 2.2M1
*/
public void readFromTemplate(DocumentReference templateDocumentReference, XWikiContext context)
throws XWikiException
{
if (templateDocumentReference != null) {
String content = getContent();
if (!content.equals("\n") && !content.equals("") && !isNew()) {
Object[] args = { getDefaultEntityReferenceSerializer().serialize(getDocumentReference()) };
throw new XWikiException(XWikiException.MODULE_XWIKI_STORE,
XWikiException.ERROR_XWIKI_APP_DOCUMENT_NOT_EMPTY,
"Cannot add a template to document {0} because it already has content", null, args);
} else {
XWiki xwiki = context.getWiki();
XWikiDocument templatedoc = xwiki.getDocument(templateDocumentReference, context);
if (templatedoc.isNew()) {
Object[] args = { getDefaultEntityReferenceSerializer().serialize(templateDocumentReference),
getCompactEntityReferenceSerializer().serialize(getDocumentReference()) };
throw new XWikiException(XWikiException.MODULE_XWIKI_STORE,
XWikiException.ERROR_XWIKI_APP_TEMPLATE_DOES_NOT_EXIST,
"Template document {0} does not exist when adding to document {1}", null, args);
} else {
setTemplateDocumentReference(templateDocumentReference);
setTitle(templatedoc.getTitle());
setContent(templatedoc.getContent());
// Set the new document syntax as the syntax of the template since the template content
// is copied into the new document
setSyntax(templatedoc.getSyntax());
// If the parent is not set in the current document set the template parent as the parent.
if (getParentReference() == null) {
setParentReference(templatedoc.getRelativeParentReference());
}
if (isNew()) {
// We might have received the objects from the cache and the template objects might have been
// copied already we need to remove them
setXObjects(new TreeMap<DocumentReference, List<BaseObject>>());
}
// Merge the external objects.
// Currently the choice is not to merge the base class and object because it is not the prefered way
// of using external classes and objects.
mergeXObjects(templatedoc);
// Copy the attachments from the template document.
copyAttachments(templatedoc);
}
}
}
}
/**
* @deprecated since 2.2M1 use {@link #readFromTemplate(DocumentReference, XWikiContext)} instead
*/
@Deprecated
public void readFromTemplate(String template, XWikiContext context) throws XWikiException
{
// Keep the same behavior for backward compatibility
DocumentReference templateDocumentReference = null;
if (StringUtils.isNotEmpty(template)) {
templateDocumentReference = getCurrentMixedDocumentReferenceResolver().resolve(template);
}
readFromTemplate(templateDocumentReference, context);
}
/**
* Use the document passed as parameter as the new identity for the current document.
*
* @param document the document containing the new identity
* @throws XWikiException in case of error
*/
private void clone(XWikiDocument document)
{
this.id = document.id;
setDocumentReference(document.getDocumentReference());
setRCSVersion(document.getRCSVersion());
setDocumentArchive(document.getDocumentArchive());
setAuthorReference(document.getAuthorReference());
setContentAuthorReference(document.getContentAuthorReference());
setContent(document.getContent());
setCreationDate(document.getCreationDate());
setDate(document.getDate());
setCustomClass(document.getCustomClass());
setContentUpdateDate(document.getContentUpdateDate());
setTitle(document.getTitle());
setFormat(document.getFormat());
setFromCache(document.isFromCache());
setElements(document.getElements());
setMeta(document.getMeta());
setMostRecent(document.isMostRecent());
setNew(document.isNew());
setStore(document.getStore());
setTemplateDocumentReference(document.getTemplateDocumentReference());
setParentReference(document.getRelativeParentReference());
setCreatorReference(document.getCreatorReference());
setDefaultLocale(document.getDefaultLocale());
setDefaultTemplate(document.getDefaultTemplate());
setValidationScript(document.getValidationScript());
setLocale(document.getLocale());
setXClass(document.getXClass().clone());
setXClassXML(document.getXClassXML());
setComment(document.getComment());
setMinorEdit(document.isMinorEdit());
setSyntax(document.getSyntax());
setHidden(document.isHidden());
cloneXObjects(document);
cloneAttachments(document);
setContentDirty(document.isContentDirty());
setMetaDataDirty(document.isMetaDataDirty());
this.elements = document.elements;
this.originalDocument = document.originalDocument;
}
@Override
public XWikiDocument clone()
{
return cloneInternal(getDocumentReference(), true);
}
/**
* Duplicate this document and give it a new name.
*
* @since 2.2.3
*/
public XWikiDocument duplicate(DocumentReference newDocumentReference)
{
return cloneInternal(newDocumentReference, false);
}
private XWikiDocument cloneInternal(DocumentReference newDocumentReference, boolean keepsIdentity)
{
XWikiDocument doc = null;
try {
Constructor<? extends XWikiDocument> constructor = getClass().getConstructor(DocumentReference.class);
doc = constructor.newInstance(newDocumentReference);
// use version field instead of getRCSVersion because it returns "1.1" if version==null.
doc.version = this.version;
doc.id = this.id;
doc.setDocumentArchive(getDocumentArchive());
doc.setAuthorReference(getAuthorReference());
doc.setContentAuthorReference(getContentAuthorReference());
doc.setContent(getContent());
doc.setCreationDate(getCreationDate());
doc.setDate(getDate());
doc.setCustomClass(getCustomClass());
doc.setContentUpdateDate(getContentUpdateDate());
doc.setTitle(getTitle());
doc.setFormat(getFormat());
doc.setFromCache(isFromCache());
doc.setElements(getElements());
doc.setMeta(getMeta());
doc.setMostRecent(isMostRecent());
doc.setNew(isNew());
doc.setStore(getStore());
doc.setTemplateDocumentReference(getTemplateDocumentReference());
doc.setParentReference(getRelativeParentReference());
doc.setCreatorReference(getCreatorReference());
doc.setDefaultLocale(getDefaultLocale());
doc.setDefaultTemplate(getDefaultTemplate());
doc.setValidationScript(getValidationScript());
doc.setLocale(getLocale());
doc.setComment(getComment());
doc.setMinorEdit(isMinorEdit());
doc.setSyntax(getSyntax());
doc.setHidden(isHidden());
if (this.xClass != null) {
doc.setXClass(this.xClass.clone());
}
if (keepsIdentity) {
doc.setXClassXML(getXClassXML());
doc.cloneXObjects(this);
doc.cloneAttachments(this);
} else {
doc.getXClass().setCustomMapping(null);
doc.duplicateXObjects(this);
doc.copyAttachments(this);
}
doc.setContentDirty(isContentDirty());
doc.setMetaDataDirty(isMetaDataDirty());
doc.elements = this.elements;
doc.originalDocument = this.originalDocument;
} catch (Exception e) {
// This should not happen
LOGGER.error("Exception while cloning document", e);
}
return doc;
}
/**
* Clone attachments from another document. This implementation expects that this document is the same as the other
* document and thus attachments will be saved in the database in the same place as the ones which they are cloning.
*
* @param sourceDocument an XWikiDocument to copy attachments from
*/
private void cloneAttachments(final XWikiDocument sourceDocument)
{
this.getAttachmentList().clear();
for (XWikiAttachment attach : sourceDocument.getAttachmentList()) {
XWikiAttachment newAttach = (XWikiAttachment) attach.clone();
// Document is set to this because if this document is renamed then the attachment will have a new id
// and be saved somewhere different.
newAttach.setDoc(this);
this.getAttachmentList().add(newAttach);
}
}
/**
* Copy attachments from one document to another. This implementation expects that you are copying the attachment
* from one document to another and thus it should be saved seperately from the original in the database.
*
* @param sourceDocument an XWikiDocument to copy attachments from
*/
public void copyAttachments(XWikiDocument sourceDocument)
{
// Note: when clearing the attachment list, we automatically mark the document's metadata as dirty.
getAttachmentList().clear();
Iterator<XWikiAttachment> attit = sourceDocument.getAttachmentList().iterator();
while (attit.hasNext()) {
XWikiAttachment attachment = attit.next();
XWikiAttachment newattachment = (XWikiAttachment) attachment.clone();
newattachment.setDoc(this);
// We need to set the content of the attachment to be dirty because the dirty bit
// is used to signal that there is a reason to save the copied attachment, otherwise
// the copied attachment will be empty since the original attachment content is not
// modified in this operation.
if (newattachment.getAttachment_content() != null) {
newattachment.getAttachment_content().setContentDirty(true);
}
getAttachmentList().add(newattachment);
}
}
/**
* Load attachment content from database.
*
* @param context the XWiki context
* @throws XWikiException when failing to load attachments
* @since 5.3M2
*/
public void loadAttachmentsContent(XWikiContext context) throws XWikiException
{
for (XWikiAttachment attachment : getAttachmentList()) {
attachment.loadContent(context);
}
}
public void loadAttachments(XWikiContext context) throws XWikiException
{
for (XWikiAttachment attachment : getAttachmentList()) {
attachment.loadContent(context);
attachment.loadArchive(context);
}
}
@Override
public boolean equals(Object object)
{
// Same Java object, they sure are equal
if (this == object) {
return true;
}
// Reference/language (document identifier)
XWikiDocument doc = (XWikiDocument) object;
if (!getDocumentReference().equals(doc.getDocumentReference())) {
return false;
}
if (!getDefaultLocale().equals(doc.getDefaultLocale())) {
return false;
}
if (!getLocale().equals(doc.getLocale())) {
return false;
}
if (getTranslation() != doc.getTranslation()) {
return false;
}
// Authors
if (ObjectUtils.notEqual(getAuthorReference(), doc.getAuthorReference())) {
return false;
}
if (ObjectUtils.notEqual(getContentAuthorReference(), doc.getContentAuthorReference())) {
return false;
}
if (ObjectUtils.notEqual(getCreatorReference(), doc.getCreatorReference())) {
return false;
}
// Version
if (!getVersion().equals(doc.getVersion())) {
return false;
}
if (getDate().getTime() != doc.getDate().getTime()) {
return false;
}
if (getContentUpdateDate().getTime() != doc.getContentUpdateDate().getTime()) {
return false;
}
if (getCreationDate().getTime() != doc.getCreationDate().getTime()) {
return false;
}
if (!getComment().equals(doc.getComment())) {
return false;
}
if (isMinorEdit() != doc.isMinorEdit()) {
return false;
}
// Datas
if (!equalsData(doc)) {
return false;
}
// We consider that 2 documents are still equal even when they have different original
// documents (see getOriginalDocument() for more details as to what is an original
// document).
return true;
}
/**
* Same as {@link #equals(Object)} but only for actual datas of the document.
* <p>
* The goal being to compare two versions of the same document this method skip every version/reference/author
* related information. For example it allows to compare a document comming from a another wiki and easily check if
* thoses actually are the same thing whatever the plumbing differences.
*
* @param otherDocument the document to compare
* @return true if bith documents have the same datas
* @since 4.1.1
*/
public boolean equalsData(XWikiDocument otherDocument)
{
// Same Java object, they sure are equal
if (this == otherDocument) {
return true;
}
if (ObjectUtils.notEqual(getParentReference(), otherDocument.getParentReference())) {
return false;
}
if (!getFormat().equals(otherDocument.getFormat())) {
return false;
}
if (!getTitle().equals(otherDocument.getTitle())) {
return false;
}
if (!getContent().equals(otherDocument.getContent())) {
return false;
}
if (!getDefaultTemplate().equals(otherDocument.getDefaultTemplate())) {
return false;
}
if (!getValidationScript().equals(otherDocument.getValidationScript())) {
return false;
}
if (ObjectUtils.notEqual(getSyntax(), otherDocument.getSyntax())) {
return false;
}
if (isHidden() != otherDocument.isHidden()) {
return false;
}
// XClass
if (!getXClass().equals(otherDocument.getXClass())) {
return false;
}
// XObjects
Set<DocumentReference> myObjectClassReferences = getXObjects().keySet();
Set<DocumentReference> otherObjectClassReferences = otherDocument.getXObjects().keySet();
if (!myObjectClassReferences.equals(otherObjectClassReferences)) {
return false;
}
for (DocumentReference reference : myObjectClassReferences) {
List<BaseObject> myObjects = getXObjects(reference);
List<BaseObject> otherObjects = otherDocument.getXObjects(reference);
if (myObjects.size() != otherObjects.size()) {
return false;
}
for (int i = 0; i < myObjects.size(); i++) {
if ((myObjects.get(i) == null && otherObjects.get(i) != null)
|| (myObjects.get(i) != null && otherObjects.get(i) == null)) {
return false;
}
if (myObjects.get(i) == null && otherObjects.get(i) == null) {
continue;
}
if (!myObjects.get(i).equals(otherObjects.get(i))) {
return false;
}
}
}
// Attachments
List<XWikiAttachment> attachments = getAttachmentList();
List<XWikiAttachment> otherAttachments = otherDocument.getAttachmentList();
if (attachments.size() != otherAttachments.size()) {
return false;
}
for (XWikiAttachment attachment : attachments) {
XWikiAttachment otherAttachment = otherDocument.getAttachment(attachment.getFilename());
try {
if (otherAttachment == null || !attachment.equalsData(otherAttachment, null)) {
return false;
}
} catch (XWikiException e) {
throw new RuntimeException(
String.format("Failed to compare attachments with reference [%s]", attachment.getReference()), e);
}
}
return true;
}
/**
* Retrieve the document in the current context language as an XML string. The rendrered document content and all
* XObjects are included. Document attachments and archived versions are excluded. You should prefer
* toXML(OutputStream, true, true, false, false, XWikiContext)} or toXML(com.xpn.xwiki.util.XMLWriter, true, true,
* false, false, XWikiContext) on the translated document when possible to reduce memory load.
*
* @param context current XWikiContext
* @return a string containing an XML representation of the document in the current context language
* @throws XWikiException when an error occurs during wiki operation
*/
public String getXMLContent(XWikiContext context) throws XWikiException
{
XWikiDocument tdoc = getTranslatedDocument(context);
return tdoc.toXML(true, true, false, false, context);
}
/**
* Retrieve the document as an XML string. All XObject are included. Rendered content, attachments and archived
* version are excluded. You should prefer toXML(OutputStream, true, false, false, false, XWikiContext)} or
* toXML(com.xpn.xwiki.util.XMLWriter, true, false, false, false, XWikiContext) when possible to reduce memory load.
*
* @param context current XWikiContext
* @return a string containing an XML representation of the document
* @throws XWikiException when an error occurs during wiki operation
*/
public String toXML(XWikiContext context) throws XWikiException
{
return toXML(true, false, false, false, context);
}
/**
* Retrieve the document as an XML string. All XObjects attachments and archived version are included. Rendered
* content is excluded. You should prefer toXML(OutputStream, true, false, true, true, XWikiContext)} or
* toXML(com.xpn.xwiki.util.XMLWriter, true, false, true, true, XWikiContext) when possible to reduce memory load.
*
* @param context current XWikiContext
* @return a string containing an XML representation of the document
* @throws XWikiException when an error occurs during wiki operation
*/
public String toFullXML(XWikiContext context) throws XWikiException
{
return toXML(true, false, true, true, context);
}
/**
* Serialize the document into a new entry of an ZipOutputStream in XML format. All XObjects and attachments are
* included. Rendered content is excluded.
*
* @param zos the ZipOutputStream to write to
* @param zipname the name of the new entry to create
* @param withVersions if true, also include archived version of the document
* @param context current XWikiContext
* @throws XWikiException when an error occurs during xwiki operations
* @throws IOException when an error occurs during streaming operations
* @since 2.3M2
* @deprecated since 4.1M2
*/
@Deprecated
public void addToZip(ZipOutputStream zos, String zipname, boolean withVersions, XWikiContext context)
throws XWikiException, IOException
{
ZipEntry zipentry = new ZipEntry(zipname);
zos.putNextEntry(zipentry);
toXML(zos, true, false, true, withVersions, context);
zos.closeEntry();
}
/**
* Serialize the document into a new entry of an ZipOutputStream in XML format. The new entry is named
* 'LastSpaceName/DocumentName'. All XObjects and attachments are included. Rendered content is excluded.
*
* @param zos the ZipOutputStream to write to
* @param withVersions if true, also include archived version of the document
* @param context current XWikiContext
* @throws XWikiException when an error occurs during xwiki operations
* @throws IOException when an error occurs during streaming operations
* @since 2.3M2
* @deprecated since 4.2M2
*/
@Deprecated
public void addToZip(ZipOutputStream zos, boolean withVersions, XWikiContext context)
throws XWikiException, IOException
{
String zipname =
getDocumentReference().getLastSpaceReference().getName() + "/" + getDocumentReference().getName();
String language = getLanguage();
if (!StringUtils.isEmpty(language)) {
zipname += "." + language;
}
addToZip(zos, zipname, withVersions, context);
}
/**
* Serialize the document into a new entry of an ZipOutputStream in XML format. The new entry is named
* 'LastSpaceName/DocumentName'. All XObjects, attachments and archived versions are included. Rendered content is
* excluded.
*
* @param zos the ZipOutputStream to write to
* @param context current XWikiContext
* @throws XWikiException when an error occurs during xwiki operations
* @throws IOException when an error occurs during streaming operations
* @since 2.3M2
* @deprecated since 4.1M2
*/
@Deprecated
public void addToZip(ZipOutputStream zos, XWikiContext context) throws XWikiException, IOException
{
addToZip(zos, true, context);
}
/**
* Serialize the document to an XML string. You should prefer
* {@link #toXML(OutputStream, boolean, boolean, boolean, boolean, XWikiContext)} or
* {@link #toXML(com.xpn.xwiki.internal.xml.XMLWriter, boolean, boolean, boolean, boolean, XWikiContext)} when
* possible to reduce memory load.
*
* @param bWithObjects include XObjects
* @param bWithRendering include the rendered content
* @param bWithAttachmentContent include attachments content
* @param bWithVersions include archived versions
* @param context current XWikiContext
* @return a string containing an XML representation of the document
* @throws XWikiException when an errors occurs during wiki operations
*/
public String toXML(boolean bWithObjects, boolean bWithRendering, boolean bWithAttachmentContent,
boolean bWithVersions, XWikiContext context) throws XWikiException
{
StringWriter writer = new StringWriter();
toXML(new DefaultWriterOutputTarget(writer), bWithObjects, bWithRendering, bWithAttachmentContent,
bWithVersions, true, context != null ? context.getWiki().getEncoding() : StandardCharsets.UTF_8.name());
return writer.toString();
}
/**
* Serialize the document to an XML {@link DOMDocument}. All XObject are included. Rendered content, attachments and
* archived version are excluded. You should prefer toXML(OutputStream, true, false, false, false, XWikiContext)} or
* toXML(com.xpn.xwiki.util.XMLWriter, true, false, false, false, XWikiContext) when possible to reduce memory load.
*
* @param context current XWikiContext
* @return a {@link DOMDocument} containing the serialized document.
* @throws XWikiException when an errors occurs during wiki operations
* @deprecated since 9.0RC1, use {@link #toXML(OutputTarget, boolean, boolean, boolean, boolean, boolean, String)}
* instead
*/
@Deprecated
public Document toXMLDocument(XWikiContext context) throws XWikiException
{
return toXMLDocument(true, false, false, false, context);
}
/**
* Serialize the document to an XML {@link DOMDocument}. You should prefer
* {@link #toXML(OutputStream, boolean, boolean, boolean, boolean, XWikiContext)} or
* {@link #toXML(com.xpn.xwiki.internal.xml.XMLWriter, boolean, boolean, boolean, boolean, XWikiContext)} when
* possible to reduce memory load.
*
* @param bWithObjects include XObjects
* @param bWithRendering include the rendered content
* @param bWithAttachmentContent include attachments content
* @param bWithVersions include archived versions
* @param context current XWikiContext
* @return a {@link DOMDocument} containing the serialized document.
* @throws XWikiException when an errors occurs during wiki operations
* @deprecated since 9.0RC1, use {@link #toXML(OutputTarget, boolean, boolean, boolean, boolean, boolean, String)}
* instead
*/
@Deprecated
public Document toXMLDocument(boolean bWithObjects, boolean bWithRendering, boolean bWithAttachmentContent,
boolean bWithVersions, XWikiContext context) throws XWikiException
{
Document doc = new DOMDocument();
DOMXMLWriter wr = new DOMXMLWriter(doc, new OutputFormat("", true, context.getWiki().getEncoding()));
try {
toXML(wr, bWithObjects, bWithRendering, bWithAttachmentContent, bWithVersions, context);
return doc;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Serialize the document to a {@link com.xpn.xwiki.internal.xml.XMLWriter}.
*
* @param bWithObjects include XObjects
* @param bWithRendering include the rendered content
* @param bWithAttachmentContent include attachments content
* @param bWithVersions include archived versions
* @param context current XWikiContext
* @throws XWikiException when an errors occurs during wiki operations
* @throws IOException when an errors occurs during streaming operations
* @since 2.3M2
* @deprecated since 9.0RC1, use {@link #toXML(OutputTarget, boolean, boolean, boolean, boolean, boolean, String)}
* instead
*/
@Deprecated
public void toXML(XMLWriter wr, boolean bWithObjects, boolean bWithRendering, boolean bWithAttachmentContent,
boolean bWithVersions, XWikiContext context) throws XWikiException, IOException
{
// IMPORTANT: we don't use directly XMLWriter's SAX apis here because it's not really working well
DocumentResult domResult = new DocumentResult();
toXML(new DefaultResultOutputTarget(domResult), bWithObjects, bWithRendering, bWithAttachmentContent,
bWithVersions, true, context != null ? context.getWiki().getEncoding() : StandardCharsets.UTF_8.name());
wr.write(domResult.getDocument().getRootElement());
}
/**
* Serialize the document to an OutputStream.
*
* @param bWithObjects include XObjects
* @param bWithRendering include the rendered content
* @param bWithAttachmentContent include attachments content
* @param bWithVersions include archived versions
* @param context current XWikiContext
* @throws XWikiException when an errors occurs during wiki operations
* @throws IOException when an errors occurs during streaming operations
* @since 2.3M2
*/
public void toXML(OutputStream out, boolean bWithObjects, boolean bWithRendering, boolean bWithAttachmentContent,
boolean bWithVersions, XWikiContext context) throws XWikiException, IOException
{
toXML(new DefaultOutputStreamOutputTarget(out), bWithObjects, bWithRendering, bWithAttachmentContent,
bWithVersions, true, context != null ? context.getWiki().getEncoding() : StandardCharsets.UTF_8.name());
}
/**
* Serialize the document to an OutputStream.
*
* @param out the output where to write the XML
* @param bWithObjects include XObjects
* @param bWithRendering include the rendered content
* @param bWithAttachmentContent include attachments content
* @param bWithVersions include archived versions
* @param format true if the XML should be formated
* @param encoding the encoding to use to write the XML
* @throws XWikiException when an errors occurs during wiki operations
* @since 9.0RC1
*/
public void toXML(OutputTarget out, boolean bWithObjects, boolean bWithRendering, boolean bWithAttachmentContent,
boolean bWithVersions, boolean format, String encoding) throws XWikiException
{
// Input
DocumentInstanceInputProperties documentProperties = new DocumentInstanceInputProperties();
documentProperties.setWithWikiObjects(bWithObjects);
documentProperties.setWithWikiDocumentContentHTML(bWithRendering);
documentProperties.setWithWikiAttachmentsContent(bWithAttachmentContent);
documentProperties.setWithJRCSRevisions(bWithVersions);
documentProperties.setWithRevisions(false);
// Output
XAROutputProperties xarProperties = new XAROutputProperties();
xarProperties.setPreserveVersion(bWithVersions);
xarProperties.setEncoding(encoding);
xarProperties.setFormat(format);
try {
Utils.getComponent(XWikiDocumentFilterUtils.class).exportEntity(this, out, xarProperties,
documentProperties);
} catch (Exception e) {
throw new XWikiException(XWikiException.MODULE_XWIKI_DOC, XWikiException.ERROR_DOC_XML_PARSING,
"Error parsing xml", e, null);
}
}
protected String encodedXMLStringAsUTF8(String xmlString)
{
if (xmlString == null) {
return "";
}
int length = xmlString.length();
char character;
StringBuilder result = new StringBuilder();
for (int i = 0; i < length; i++) {
character = xmlString.charAt(i);
switch (character) {
case '&':
result.append("&");
break;
case '"':
result.append(""");
break;
case '<':
result.append("<");
break;
case '>':
result.append(">");
break;
case '\n':
result.append("\n");
break;
case '\r':
result.append("\r");
break;
case '\t':
result.append("\t");
break;
default:
if (character < 0x20) {
} else if (character > 0x7F) {
result.append("");
result.append(Integer.toHexString(character).toUpperCase());
result.append(";");
} else {
result.append(character);
}
break;
}
}
return result.toString();
}
protected String getElement(Element docel, String name)
{
Element el = docel.element(name);
if (el == null) {
return "";
} else {
return el.getText();
}
}
public void fromXML(String xml) throws XWikiException
{
fromXML(xml, false);
}
public void fromXML(InputStream is) throws XWikiException
{
fromXML(is, false);
}
public void fromXML(InputSource source, boolean withArchive) throws XWikiException
{
// Output
DocumentInstanceOutputProperties documentProperties = new DocumentInstanceOutputProperties();
XWikiContext xcontext = getXWikiContext();
if (xcontext != null) {
documentProperties.setDefaultReference(getXWikiContext().getWikiReference());
}
documentProperties.setVersionPreserved(withArchive);
// Input
XARInputProperties xarProperties = new XARInputProperties();
xarProperties.setWithHistory(withArchive);
try {
Utils.getComponent(XWikiDocumentFilterUtils.class).importEntity(XWikiDocument.class, this, source,
xarProperties, documentProperties);
} catch (Exception e) {
throw new XWikiException(XWikiException.MODULE_XWIKI_DOC, XWikiException.ERROR_DOC_XML_PARSING,
"Error parsing xml", e, null);
}
// We have been reading from XML so the document does not need a new version when saved
setMetaDataDirty(false);
setContentDirty(false);
}
public void fromXML(String source, boolean withArchive) throws XWikiException
{
fromXML(new StringInputSource(source), withArchive);
}
public void fromXML(InputStream source, boolean withArchive) throws XWikiException
{
fromXML(new DefaultInputStreamInputSource(source), withArchive);
}
/**
* @deprecated since 9.0RC1, use {@link #fromXML(InputStream)} instead
*/
@Deprecated
public void fromXML(Document domdoc, boolean withArchive) throws XWikiException
{
// Serialize the Document (could not find a way to convert a dom4j Document into a usable StAX source)
StringWriter writer = new StringWriter();
try {
org.dom4j.io.XMLWriter domWriter = new org.dom4j.io.XMLWriter(writer);
domWriter.write(domdoc);
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(), withArchive);
}
/**
* Check if provided xml document is a wiki document.
*
* @param domdoc the xml document.
* @return true if provided xml document is a wiki document.
*/
public static boolean containsXMLWikiDocument(Document domdoc)
{
return domdoc.getRootElement().getName().equals(XarDocumentModel.ELEMENT_DOCUMENT);
}
public void setAttachmentList(List<XWikiAttachment> list)
{
// For backwards compatibility reasons (and in general), we need to allow callers to do something like
// setAttachmentList(getAttachmentList())
if (this.attachmentList != list) {
this.attachmentList.clear();
this.attachmentList.addAll(list);
}
}
public List<XWikiAttachment> getAttachmentList()
{
return this.attachmentList;
}
public void saveAllAttachments(XWikiContext context) throws XWikiException
{
saveAllAttachments(true, true, context);
}
public void saveAllAttachments(boolean updateParent, boolean transaction, XWikiContext context)
throws XWikiException
{
for (XWikiAttachment attachment : this.attachmentList) {
saveAttachmentContent(attachment, false, transaction, context);
}
// Save the document
if (updateParent) {
context.getWiki().saveDocument(this, context);
}
}
public void saveAttachmentsContent(List<XWikiAttachment> attachments, XWikiContext context) throws XWikiException
{
String database = context.getWikiId();
try {
// We might need to switch database to get the translated content
if (getDatabase() != null) {
context.setWikiId(getDatabase());
}
context.getWiki().getAttachmentStore().saveAttachmentsContent(attachments, this, true, context, true);
} catch (OutOfMemoryError e) {
throw new XWikiException(XWikiException.MODULE_XWIKI_APP, XWikiException.ERROR_XWIKI_APP_JAVA_HEAP_SPACE,
"Out Of Memory Exception");
} finally {
if (database != null) {
context.setWikiId(database);
}
}
}
public void saveAttachmentContent(XWikiAttachment attachment, XWikiContext context) throws XWikiException
{
saveAttachmentContent(attachment, true, true, context);
}
public void saveAttachmentContent(XWikiAttachment attachment, boolean updateParent, boolean transaction,
XWikiContext context) throws XWikiException
{
String currentWiki = context.getWikiId();
try {
// We might need to switch database to
// get the translated content
if (getDatabase() != null) {
context.setWikiId(getDatabase());
}
// Save the attachment
context.getWiki().getAttachmentStore().saveAttachmentContent(attachment, false, context, transaction);
// We need to make sure there is a version upgrade
setMetaDataDirty(true);
// Save the document
if (updateParent) {
context.getWiki().saveDocument(this, context);
}
} catch (OutOfMemoryError e) {
throw new XWikiException(XWikiException.MODULE_XWIKI_APP, XWikiException.ERROR_XWIKI_APP_JAVA_HEAP_SPACE,
"Out Of Memory Exception");
} finally {
if (currentWiki != null) {
context.setWikiId(currentWiki);
}
}
}
public void loadAttachmentContent(XWikiAttachment attachment, XWikiContext context) throws XWikiException
{
String database = context.getWikiId();
try {
// We might need to switch database to
// get the translated content
if (getDatabase() != null) {
context.setWikiId(getDatabase());
}
context.getWiki().getAttachmentStore().loadAttachmentContent(attachment, context, true);
} finally {
if (database != null) {
context.setWikiId(database);
}
}
}
/**
* Remove the attachment from the document attachment list and put it in the list of attachments to remove for next
* document save.
* <p>
* The attachment will be move to recycle bin.
*
* @param attachment the attachment to remove
* @return the removed attachment, null if none could be found
* @since 5.2M1
*/
public XWikiAttachment removeAttachment(XWikiAttachment attachment)
{
return removeAttachment(attachment, true);
}
/**
* Remove the attachment from the document attachment list and put it in the list of attachments to remove for next
* document save.
*
* @param attachmentToRemove the attachment to remove
* @param toRecycleBin indicate if the attachment should be moved to recycle bin
* @return the removed attachment, null if none could be found
* @since 5.2M1
*/
public XWikiAttachment removeAttachment(XWikiAttachment attachmentToRemove, boolean toRecycleBin)
{
List<XWikiAttachment> list = getAttachmentList();
for (int i = 0; i < list.size(); i++) {
XWikiAttachment attachment = list.get(i);
if (attachmentToRemove.getFilename().equals(attachment.getFilename())) {
list.remove(i);
this.attachmentsToRemove.add(new XWikiAttachmentToRemove(attachment, toRecycleBin));
setMetaDataDirty(true);
return attachment;
}
}
return null;
}
/**
* @return the attachment planned for removal
*/
public List<XWikiAttachmentToRemove> getAttachmentsToRemove()
{
return this.attachmentsToRemove;
}
/**
* Clear the list of attachments planned for removal.
*/
public void clearAttachmentsToRemove()
{
this.attachmentsToRemove.clear();
}
/**
* Get the wiki document references pointing to this document.
* <p>
* Theses links are stored to the database when documents are saved. You can use "backlinks" in XWikiPreferences or
* "xwiki.backlinks" in xwiki.cfg file to enable links storage in the database.
*
* @param context the XWiki context.
* @return the found wiki document references
* @throws XWikiException error when getting pages names from database.
* @since 2.2M2
*/
public List<DocumentReference> getBackLinkedReferences(XWikiContext context) throws XWikiException
{
return getStore(context).loadBacklinks(getDocumentReference(), true, context);
}
/**
* @deprecated since 2.2M2 use {@link #getBackLinkedReferences(XWikiContext)}
*/
@Deprecated
public List<String> getBackLinkedPages(XWikiContext context) throws XWikiException
{
return getStore(context).loadBacklinks(getFullName(), context, true);
}
/**
* Get a list of unique links from this document to others documents.
* <ul>
* <li>xwiki/1.0 content: get the unique links associated to document from database. This is stored when the
* document is saved. You can use "backlinks" in XWikiPreferences or "xwiki.backlinks" in xwiki.cfg file to enable
* links storage in the database.</li>
* <li>Other content: call {@link #getUniqueLinkedPages(XWikiContext)} and generate the List.</li>
* </ul>
*
* @param context the XWiki context
* @return the found wiki links.
* @throws XWikiException error when getting links from database when xwiki/1.0 content.
* @since 1.9M2
*/
public Set<XWikiLink> getUniqueWikiLinkedPages(XWikiContext context) throws XWikiException
{
Set<XWikiLink> links;
if (is10Syntax()) {
links = new LinkedHashSet<>(getStore(context).loadLinks(getId(), context, true));
} else {
Set<String> linkedPages = getUniqueLinkedPages(context);
links = new LinkedHashSet<>(linkedPages.size());
for (String linkedPage : linkedPages) {
XWikiLink wikiLink = new XWikiLink();
wikiLink.setDocId(getId());
wikiLink.setFullName(LOCAL_REFERENCE_SERIALIZER.serialize(getDocumentReference()));
wikiLink.setLink(linkedPage);
links.add(wikiLink);
}
}
return links;
}
/**
* Extract all the unique static (i.e. not generated by macros) wiki links (pointing to wiki page) from this
* xwiki/1.0 document's content to others documents.
*
* @param context the XWiki context.
* @return the document names for linked pages, if null an error append.
* @since 1.9M2
*/
private Set<String> getUniqueLinkedPages10(XWikiContext context)
{
Set<String> pageNames;
try {
List<String> list = context.getUtil().getUniqueMatches(getContent(), "\\[(.*?)\\]", 1);
pageNames = new HashSet<String>(list.size());
DocumentReference currentDocumentReference = getDocumentReference();
for (String name : list) {
int i1 = name.indexOf('>');
if (i1 != -1) {
name = name.substring(i1 + 1);
}
i1 = name.indexOf(">");
if (i1 != -1) {
name = name.substring(i1 + 4);
}
i1 = name.indexOf('#');
if (i1 != -1) {
name = name.substring(0, i1);
}
i1 = name.indexOf('?');
if (i1 != -1) {
name = name.substring(0, i1);
}
// Let's get rid of anything that's not a real link
if (name.trim().equals("") || (name.indexOf('$') != -1) || (name.indexOf("://") != -1)
|| (name.indexOf('"') != -1) || (name.indexOf('\'') != -1) || (name.indexOf("..") != -1)
|| (name.indexOf(':') != -1) || (name.indexOf('=') != -1)) {
continue;
}
// generate the link
String newname = StringUtils.replace(Util.noaccents(name), " ", "");
// If it is a local link let's add the space
if (newname.indexOf('.') == -1) {
newname = getSpace() + "." + name;
}
if (context.getWiki().exists(newname, context)) {
name = newname;
} else {
// If it is a local link let's add the space
if (name.indexOf('.') == -1) {
name = getSpace() + "." + name;
}
}
// If the reference is empty, the link is an autolink
if (!StringUtils.isEmpty(name)) {
// The reference may not have the space or even document specified (in case of an empty
// string)
// Thus we need to find the fully qualified document name
DocumentReference documentReference = getCurrentDocumentReferenceResolver().resolve(name);
// Verify that the link is not an autolink (i.e. a link to the current document)
if (!documentReference.equals(currentDocumentReference)) {
pageNames.add(getCompactEntityReferenceSerializer().serialize(documentReference));
}
}
}
return pageNames;
} catch (Exception e) {
// This should never happen
LOGGER.error("Failed to get linked documents", e);
return null;
}
}
/**
* Extract all the unique static (i.e. not generated by macros) wiki links (pointing to wiki page) from this
* document's content to others documents.
*
* @param context the XWiki context.
* @return the document names for linked pages, if null an error append.
* @since 1.9M2
*/
public Set<String> getUniqueLinkedPages(XWikiContext context)
{
Set<String> pageNames;
XWikiDocument contextDoc = context.getDoc();
String contextWiki = context.getWikiId();
try {
// Make sure the right document is used as context document
context.setDoc(this);
// Make sure the right wiki is used as context document
context.setWikiId(getDatabase());
if (is10Syntax()) {
pageNames = getUniqueLinkedPages10(context);
} else {
XDOM dom = getXDOM();
// TODO: Add support for macro as well.
List<LinkBlock> linkBlocks =
dom.getBlocks(new ClassBlockMatcher(LinkBlock.class), Block.Axes.DESCENDANT);
pageNames = new LinkedHashSet<String>(linkBlocks.size());
DocumentReference currentDocumentReference = getDocumentReference();
for (LinkBlock linkBlock : linkBlocks) {
ResourceReference reference = linkBlock.getReference();
String referenceString = reference.getReference();
ResourceType resourceType = reference.getType();
// TODO: Add support for ATTACHMENT as well.
if (!ResourceType.DOCUMENT.equals(resourceType) && !ResourceType.SPACE.equals(resourceType)) {
// We are only interested in Document or Space references.
continue;
}
// Optimisation: If the reference is empty, the link is an autolink and we don`t include it.
if (StringUtils.isEmpty(referenceString)) {
continue;
}
// FIXME?: pass this.getDocumentReference as parameter to the resolve so that relative references
// get resolved relative to this and not to the context document.
EntityReference documentReference =
getResourceReferenceEntityReferenceResolver().resolve(reference, EntityType.DOCUMENT);
// Verify after resolving it that the link is not an autolink(i.e. a link to the current document)
if (!documentReference.equals(currentDocumentReference)) {
// Since this method is used for saving backlinks and since backlinks must be
// saved with the space and page name but without the wiki part, we remove the wiki
// part before serializing.
// This is a bit of a hack since the default serializer should theoretically fail
// if it's passed an invalid reference.
pageNames.add(getCompactWikiEntityReferenceSerializer().serialize(documentReference));
}
}
}
} finally {
context.setDoc(contextDoc);
context.setWikiId(contextWiki);
}
return pageNames;
}
/**
* Returns a list of references of all documents which list this document as their parent, in the current wiki.
* {@link #getChildren(int, int, com.xpn.xwiki.XWikiContext)}
*
* @since 2.2M2
*/
public List<DocumentReference> getChildrenReferences(XWikiContext context) throws XWikiException
{
return getChildrenReferences(0, 0, context);
}
/**
* @deprecated since 2.2M2 use {@link #getChildrenReferences(XWikiContext)}
*/
@Deprecated
public List<String> getChildren(XWikiContext context) throws XWikiException
{
return getChildren(0, 0, context);
}
/**
* Returns a list of references of all documents which list this document as their parent, in the current wiki.
*
* @param nb The number of results to return.
* @param start The number of results to skip before we begin returning results.
* @param context The {@link com.xpn.xwiki.XWikiContext context}.
* @return the list of document references
* @throws XWikiException If there's an error querying the database.
* @since 2.2M2
*/
public List<DocumentReference> getChildrenReferences(int nb, int start, XWikiContext context) throws XWikiException
{
// Use cases:
// - the parent document reference saved in the database matches the reference of this document, in its fully
// serialized form (eg "wiki:space.page"). Note that this is normally not required since the wiki part
// isn't saved in the database when it matches the current wiki.
// - the parent document reference saved in the database matches the reference of this document, in its
// serialized form without the wiki part (eg "space.page"). The reason we don't need to specify the wiki
// part is because document parents saved in the database don't have the wiki part specified when it matches
// the current wiki.
// - the parent document reference saved in the database matches the page name part of this document's
// reference (eg "page") and the parent document's space is the same as this document's space.
List<DocumentReference> children = new ArrayList<DocumentReference>();
try {
Query query = getStore().getQueryManager().createQuery(
"select distinct doc.fullName from XWikiDocument doc where "
+ "doc.parent=:prefixedFullName or doc.parent=:fullName or (doc.parent=:name and doc.space=:space)",
Query.XWQL);
query.addFilter(Utils.getComponent(QueryFilter.class, "hidden"));
query.bindValue("prefixedFullName",
getDefaultEntityReferenceSerializer().serialize(getDocumentReference()));
query.bindValue("fullName", LOCAL_REFERENCE_SERIALIZER.serialize(getDocumentReference()));
query.bindValue("name", getDocumentReference().getName());
query.bindValue("space",
LOCAL_REFERENCE_SERIALIZER.serialize(getDocumentReference().getLastSpaceReference()));
query.setLimit(nb).setOffset(start);
List<String> queryResults = query.execute();
WikiReference wikiReference = this.getDocumentReference().getWikiReference();
for (String fullName : queryResults) {
children.add(getCurrentDocumentReferenceResolver().resolve(fullName, wikiReference));
}
} catch (QueryException e) {
throw new XWikiException(XWikiException.MODULE_XWIKI_STORE, XWikiException.ERROR_XWIKI_UNKNOWN,
String.format("Failed to retrieve children for document [%s]", this.getDocumentReference()), e);
}
return children;
}
/**
* @deprecated since 2.2M2 use {@link #getChildrenReferences(XWikiContext)}
*/
@Deprecated
public List<String> getChildren(int nb, int start, XWikiContext context) throws XWikiException
{
List<String> childrenNames = new ArrayList<String>();
for (DocumentReference reference : getChildrenReferences(nb, start, context)) {
childrenNames.add(LOCAL_REFERENCE_SERIALIZER.serialize(reference));
}
return childrenNames;
}
/**
* @since 2.2M2
*/
public void renameProperties(DocumentReference classReference, Map<String, String> fieldsToRename)
{
List<BaseObject> objects = this.xObjects.get(classReference);
if (objects == null) {
return;
}
boolean isDirty = false;
for (BaseObject bobject : objects) {
if (bobject == null) {
continue;
}
for (Map.Entry<String, String> entry : fieldsToRename.entrySet()) {
String origname = entry.getKey();
String newname = entry.getValue();
BaseProperty origprop = (BaseProperty) bobject.safeget(origname);
if (origprop != null) {
BaseProperty prop = origprop.clone();
bobject.removeField(origname);
prop.setName(newname);
bobject.addField(newname, prop);
isDirty = true;
}
}
}
// If at least one property was renamed, mark the document dirty.
if (isDirty) {
setMetaDataDirty(true);
}
}
/**
* @deprecated since 2.2M2 use {@link #renameProperties(DocumentReference, Map)} instead
*/
@Deprecated
public void renameProperties(String className, Map<String, String> fieldsToRename)
{
renameProperties(resolveClassReference(className), fieldsToRename);
}
/**
* @since 2.2M1
*/
public void addXObjectToRemove(BaseObject object)
{
getXObjectsToRemove().add(object);
object.setOwnerDocument(null);
setMetaDataDirty(true);
}
/**
* Automatically add objects present in the old version, but not in the current document, to the list of objects
* marked for removal from the database.
*
* @param previousVersion the version of the document present in the database
* @since 3.3M2
*/
public void addXObjectsToRemoveFromVersion(XWikiDocument previousVersion)
{
if (previousVersion == null) {
return;
}
for (List<BaseObject> objects : previousVersion.getXObjects().values()) {
for (BaseObject originalObj : objects) {
if (originalObj != null) {
BaseObject newObj = getXObject(originalObj.getXClassReference(), originalObj.getNumber());
if (newObj == null) {
// The object was deleted.
this.addXObjectToRemove(originalObj);
}
}
}
}
}
/**
* @deprecated since 2.2M2 use {@link #addXObjectToRemove(BaseObject)} )} instead
*/
@Deprecated
public void addObjectsToRemove(BaseObject object)
{
addXObjectToRemove(object);
}
/**
* @since 2.2M2
*/
public List<BaseObject> getXObjectsToRemove()
{
return this.xObjectsToRemove;
}
/**
* @deprecated since 2.2M2 use {@link #getObjectsToRemove()} instead
*/
@Deprecated
public ArrayList<BaseObject> getObjectsToRemove()
{
return (ArrayList<BaseObject>) getXObjectsToRemove();
}
/**
* @since 2.2M1
*/
public void setXObjectsToRemove(List<BaseObject> objectsToRemove)
{
this.xObjectsToRemove = objectsToRemove;
setMetaDataDirty(true);
}
public List<String> getIncludedPages(XWikiContext context)
{
try {
return getIncludedPagesInternal(context);
} catch (Exception e) {
// If an error happens then we return an empty list of included pages. We don't want to fail by throwing an
// exception since it'll lead to several errors in the UI (such as in the Information Panel in edit mode).
LOGGER.error("Failed to get included pages for [{}]", getDocumentReference(), e);
return Collections.emptyList();
}
}
private List<String> getIncludedPagesInternal(XWikiContext context)
{
if (is10Syntax()) {
return getIncludedPagesForXWiki10Syntax(getContent(), context);
} else {
// Find all include macros listed on the page
XDOM dom = getXDOM();
List<String> result = new ArrayList<String>();
List<MacroBlock> macroBlocks =
dom.getBlocks(new ClassBlockMatcher(MacroBlock.class), Block.Axes.DESCENDANT);
for (MacroBlock macroBlock : macroBlocks) {
// - Add each document pointed to by the include macro
// - Also add all the included pages found in the velocity macro when using the deprecated #include*
// macros
// This should be removed when we fully drop support for the XWiki Syntax 1.0 but for now we want to
// play nice with people migrating from 1.0 to 2.0 syntax
if (macroBlock.getId().equalsIgnoreCase("include") || macroBlock.getId().equalsIgnoreCase("display")) {
String documentName = macroBlock.getParameters().get("reference");
if (StringUtils.isEmpty(documentName)) {
documentName = macroBlock.getParameters().get("document");
if (StringUtils.isEmpty(documentName)) {
continue;
}
}
DocumentReference documentReference =
getExplicitDocumentReferenceResolver().resolve(documentName, getDocumentReference());
if (this.getDocumentReference().equals(documentReference)) {
// Skip auto-includes since they are not allowed anyway.
continue;
}
documentName = LOCAL_REFERENCE_SERIALIZER.serialize(documentReference);
result.add(documentName);
} else if (macroBlock.getId().equalsIgnoreCase("velocity")
&& !StringUtils.isEmpty(macroBlock.getContent())) {
// Try to find matching content inside each velocity macro
result.addAll(getIncludedPagesForXWiki10Syntax(macroBlock.getContent(), context));
}
}
return result;
}
}
private List<String> getIncludedPagesForXWiki10Syntax(String content, XWikiContext context)
{
try {
String pattern = "#include(Topic|InContext|Form|Macros|parseGroovyFromPage)\\([\"'](.*?)[\"']\\)";
List<String> list = context.getUtil().getUniqueMatches(content, pattern, 2);
for (int i = 0; i < list.size(); i++) {
String name = list.get(i);
if (name.indexOf('.') == -1) {
list.set(i, getSpace() + "." + name);
}
}
return list;
} catch (Exception e) {
LOGGER.error("Failed to extract include target from provided content [" + content + "]", e);
return null;
}
}
public List<String> getIncludedMacros(XWikiContext context)
{
return context.getWiki().getIncludedMacros(getSpace(), getContent(), context);
}
public String displayRendered(PropertyClass pclass, String prefix, BaseCollection object, XWikiContext context)
throws XWikiException
{
String result = pclass.displayView(pclass.getName(), prefix, object, context);
return getRenderedContent(result, Syntax.XWIKI_1_0.toIdString(), context);
}
public String displayView(PropertyClass pclass, String prefix, BaseCollection object, XWikiContext context)
{
return (pclass == null) ? "" : pclass.displayView(pclass.getName(), prefix, object, context);
}
public String displayEdit(PropertyClass pclass, String prefix, BaseCollection object, XWikiContext context)
{
return (pclass == null) ? "" : pclass.displayEdit(pclass.getName(), prefix, object, context);
}
public String displayHidden(PropertyClass pclass, String prefix, BaseCollection object, XWikiContext context)
{
return (pclass == null) ? "" : pclass.displayHidden(pclass.getName(), prefix, object, context);
}
/**
* @param filename the file name of the attachment with or without the extension
* @return the {@link XWikiAttachment} corresponding to the file name, null if none can be found
*/
public XWikiAttachment getAttachment(String filename)
{
for (XWikiAttachment attach : getAttachmentList()) {
if (attach.getFilename().equals(filename)) {
return attach;
}
}
for (XWikiAttachment attach : getAttachmentList()) {
if (attach.getFilename().startsWith(filename + ".")) {
return attach;
}
}
return null;
}
/**
* Add passed attachment to the document.
*
* @param attachment the attachment to add
* @since 5.3M2
*/
public void addAttachment(XWikiAttachment attachment)
{
attachment.setDoc(this);
getAttachmentList().add(attachment);
}
/**
* @deprecated use {@link #addAttachment(String, InputStream, XWikiContext)} instead
*/
@Deprecated
public XWikiAttachment addAttachment(String fileName, byte[] content, XWikiContext context) throws XWikiException
{
try {
return addAttachment(fileName, new ByteArrayInputStream(content != null ? content : new byte[0]), context);
} catch (IOException e) {
throw new XWikiException(XWikiException.MODULE_XWIKI_DOC, XWikiException.ERROR_XWIKI_UNKNOWN,
"Failed to set Attachment content", e);
}
}
public XWikiAttachment addAttachment(String fileName, InputStream content, XWikiContext context)
throws XWikiException, IOException
{
int i = fileName.indexOf('\\');
if (i == -1) {
i = fileName.indexOf('/');
}
String filename = fileName.substring(i + 1);
XWikiAttachment attachment = getAttachment(filename);
if (attachment == null) {
attachment = new XWikiAttachment(this, filename);
// Add the attachment in the current doc
getAttachmentList().add(attachment);
}
attachment.setContent(content);
attachment.setAuthorReference(context.getUserReference());
return attachment;
}
public BaseObject getFirstObject(String fieldname)
{
// Keeping this function with context null for compatibility reasons.
// It should not be used, since it would miss properties which are only defined in the class
// and not present in the object because the object was not updated
return getFirstObject(fieldname, null);
}
public BaseObject getFirstObject(String fieldname, XWikiContext context)
{
Collection<List<BaseObject>> objectscoll = getXObjects().values();
if (objectscoll == null) {
return null;
}
for (List<BaseObject> objects : objectscoll) {
for (BaseObject obj : objects) {
if (obj != null) {
BaseClass bclass = obj.getXClass(context);
if (bclass != null) {
Set<String> set = bclass.getPropertyList();
if ((set != null) && set.contains(fieldname)) {
return obj;
}
}
Set<String> set = obj.getPropertyList();
if ((set != null) && set.contains(fieldname)) {
return obj;
}
}
}
}
return null;
}
/**
* @since 2.2.3
*/
public void setProperty(EntityReference classReference, String fieldName, BaseProperty value)
{
BaseObject bobject = prepareXObject(classReference);
bobject.safeput(fieldName, value);
}
/**
* @deprecated since 2.2M2 use {@link #setProperty(EntityReference, String, BaseProperty)} instead
*/
@Deprecated
public void setProperty(String className, String fieldName, BaseProperty value)
{
setProperty(getXClassEntityReferenceResolver().resolve(className, EntityType.DOCUMENT, getDocumentReference()),
fieldName, value);
}
/**
* @since 2.2M2
*/
public int getIntValue(DocumentReference classReference, String fieldName)
{
BaseObject obj = getXObject(classReference, 0);
if (obj == null) {
return 0;
}
return obj.getIntValue(fieldName);
}
/**
* @deprecated since 2.2M2 use {@link #getIntValue(DocumentReference, String)} instead
*/
@Deprecated
public int getIntValue(String className, String fieldName)
{
return getIntValue(resolveClassReference(className), fieldName);
}
/**
* @since 2.2M2
*/
public long getLongValue(DocumentReference classReference, String fieldName)
{
BaseObject obj = getXObject(classReference, 0);
if (obj == null) {
return 0;
}
return obj.getLongValue(fieldName);
}
/**
* @deprecated since 2.2M2 use {@link #getLongValue(DocumentReference, String)} instead
*/
@Deprecated
public long getLongValue(String className, String fieldName)
{
return getLongValue(resolveClassReference(className), fieldName);
}
/**
* @since 6.2M1
*/
public String getStringValue(EntityReference classReference, String fieldName)
{
return getStringValue(resolveClassReference(classReference), fieldName);
}
/**
* @since 2.2M2
*/
public String getStringValue(DocumentReference classReference, String fieldName)
{
BaseObject obj = getXObject(classReference);
if (obj == null) {
return "";
}
String result = obj.getStringValue(fieldName);
if (result.equals(" ")) {
return "";
} else {
return result;
}
}
/**
* @deprecated since 2.2M2 use {@link #getStringValue(DocumentReference, String)} instead
*/
@Deprecated
public String getStringValue(String className, String fieldName)
{
return getStringValue(resolveClassReference(className), fieldName);
}
public int getIntValue(String fieldName)
{
BaseObject object = getFirstObject(fieldName, null);
if (object == null) {
return 0;
} else {
return object.getIntValue(fieldName);
}
}
public long getLongValue(String fieldName)
{
BaseObject object = getFirstObject(fieldName, null);
if (object == null) {
return 0;
} else {
return object.getLongValue(fieldName);
}
}
public String getStringValue(String fieldName)
{
BaseObject object = getFirstObject(fieldName, null);
if (object == null) {
return "";
}
String result = object.getStringValue(fieldName);
if (result.equals(" ")) {
return "";
} else {
return result;
}
}
/**
* @since 2.2.3
*/
public void setStringValue(EntityReference classReference, String fieldName, String value)
{
BaseObject bobject = prepareXObject(classReference);
bobject.setStringValue(fieldName, value);
}
/**
* @deprecated since 2.2M2 use {@link #setStringValue(EntityReference, String, String)} instead
*/
@Deprecated
public void setStringValue(String className, String fieldName, String value)
{
setStringValue(
getXClassEntityReferenceResolver().resolve(className, EntityType.DOCUMENT, getDocumentReference()),
fieldName, value);
}
/**
* @since 2.2M2
*/
public List getListValue(DocumentReference classReference, String fieldName)
{
BaseObject obj = getXObject(classReference);
if (obj == null) {
return new ArrayList();
}
return obj.getListValue(fieldName);
}
/**
* @deprecated since 2.2M2 use {@link #getListValue(DocumentReference, String)} instead
*/
@Deprecated
public List getListValue(String className, String fieldName)
{
return getListValue(resolveClassReference(className), fieldName);
}
public List getListValue(String fieldName)
{
BaseObject object = getFirstObject(fieldName, null);
if (object == null) {
return new ArrayList();
}
return object.getListValue(fieldName);
}
/**
* @since 2.2.3
*/
public void setStringListValue(EntityReference classReference, String fieldName, List value)
{
BaseObject bobject = prepareXObject(classReference);
bobject.setStringListValue(fieldName, value);
}
/**
* @deprecated since 2.2M2 use {@link #setStringListValue(EntityReference, String, List)} instead
*/
@Deprecated
public void setStringListValue(String className, String fieldName, List value)
{
setStringListValue(
getXClassEntityReferenceResolver().resolve(className, EntityType.DOCUMENT, getDocumentReference()),
fieldName, value);
}
/**
* @since 2.2.3
*/
public void setDBStringListValue(EntityReference classReference, String fieldName, List value)
{
BaseObject bobject = prepareXObject(classReference);
bobject.setDBStringListValue(fieldName, value);
}
/**
* @deprecated since 2.2M2 use {@link #setDBStringListValue(EntityReference, String, List)} instead
*/
@Deprecated
public void setDBStringListValue(String className, String fieldName, List value)
{
setDBStringListValue(
getXClassEntityReferenceResolver().resolve(className, EntityType.DOCUMENT, getDocumentReference()),
fieldName, value);
}
/**
* @since 2.2.3
*/
public void setLargeStringValue(EntityReference classReference, String fieldName, String value)
{
BaseObject bobject = prepareXObject(classReference);
bobject.setLargeStringValue(fieldName, value);
}
/**
* @deprecated since 2.2M2 use {@link #setLargeStringValue(EntityReference, String, String)} instead
*/
@Deprecated
public void setLargeStringValue(String className, String fieldName, String value)
{
setLargeStringValue(
getXClassEntityReferenceResolver().resolve(className, EntityType.DOCUMENT, getDocumentReference()),
fieldName, value);
}
/**
* @since 2.2.3
*/
public void setIntValue(EntityReference classReference, String fieldName, int value)
{
BaseObject bobject = prepareXObject(classReference);
bobject.setIntValue(fieldName, value);
}
/**
* @deprecated since 2.2M2 use {@link #setIntValue(EntityReference, String, int)} instead
*/
@Deprecated
public void setIntValue(String className, String fieldName, int value)
{
setIntValue(getXClassEntityReferenceResolver().resolve(className, EntityType.DOCUMENT, getDocumentReference()),
fieldName, value);
}
/**
* Note that this method cannot be removed for now since it's used by Hibernate for saving a XWikiDocument.
*
* @deprecated since 2.2M1 use {@link #getDocumentReference()} instead
*/
@Deprecated
public String getDatabase()
{
return getDocumentReference().getWikiReference().getName();
}
/**
* Note that this method cannot be removed for now since it's used by Hibernate for loading a XWikiDocument.
*
* @deprecated since 2.2M1 use {@link #setDocumentReference(DocumentReference)} instead
*/
@Deprecated
public void setDatabase(String database)
{
if (database != null) {
DocumentReference reference = getDocumentReference();
WikiReference wiki = reference.getWikiReference();
WikiReference newWiki = new WikiReference(database);
if (!newWiki.equals(wiki)) {
setDocumentReferenceInternal(reference.replaceParent(wiki, newWiki));
}
}
}
/**
* Note that this method cannot be removed for now since it's used by Hibernate for saving a XWikiDocument.
*
* @deprecated since 4.3M2 use {@link #getLocale()} instead
*/
@Deprecated
public String getLanguage()
{
return getLocale().toString();
}
/**
* Note that this method cannot be removed for now since it's used by Hibernate for saving a XWikiDocument.
*
* @deprecated since 4.3M2 use {@link #setLocale(Locale)} instead
*/
@Deprecated
public void setLanguage(String language)
{
setLocale(LocaleUtils.toLocale(Util.normalizeLanguage(language), Locale.ROOT));
}
/**
* @return the locale of the document
*/
public Locale getLocale()
{
return this.locale != null ? this.locale : Locale.ROOT;
}
/**
* @param locale the locale of the document
*/
public void setLocale(Locale locale)
{
this.locale = locale;
setMetaDataDirty(true);
// Clean various caches
this.keyCache = null;
this.localKeyCache = null;
}
/**
* Note that this method cannot be removed for now since it's used by Hibernate for saving a XWikiDocument.
*
* @deprecated since 4.3M2 use {@link #getDefaultLocale()} instead
*/
@Deprecated
public String getDefaultLanguage()
{
return getDefaultLocale().toString();
}
/**
* Note that this method cannot be removed for now since it's used by Hibernate for saving a XWikiDocument.
*
* @deprecated since 4.3M2 use {@link #setDefaultLocale(Locale)} instead
*/
@Deprecated
public void setDefaultLanguage(String defaultLanguage)
{
setDefaultLocale(LocaleUtils.toLocale(defaultLanguage, Locale.ROOT));
}
public Locale getDefaultLocale()
{
return this.defaultLocale != null ? this.defaultLocale : Locale.ROOT;
}
public void setDefaultLocale(Locale defaultLocale)
{
this.defaultLocale = defaultLocale;
setMetaDataDirty(true);
}
public int getTranslation()
{
return getLocale().equals(Locale.ROOT) ? 0 : 1;
}
/**
* Note that this method cannot be removed for now since it's called by Hibernate when loading a XWikiDocument.
*
* @deprecated since 5.4.6, stored in the database to speedup some queries (really ?) but in {@link XWikiDocument}
* it's calculated based on the document locale
*/
@Deprecated
public void setTranslation(int translation)
{
// Do nothing
}
public String getTranslatedContent(XWikiContext context) throws XWikiException
{
String language = context.getWiki().getLanguagePreference(context);
return getTranslatedContent(language, context);
}
public String getTranslatedContent(String locale, XWikiContext context) throws XWikiException
{
XWikiDocument tdoc = getTranslatedDocument(locale, context);
return tdoc.getContent();
}
public XWikiDocument getTranslatedDocument(XWikiContext context) throws XWikiException
{
String locale = context.getWiki().getLanguagePreference(context);
return getTranslatedDocument(locale, context);
}
/**
* Return the document in the provided language.
* <p>
* This method return this if the provided language does not exists. See
*
* @param language the language of the document to return
* @param context the XWiki Context
* @return the document in the provided language or this if the provided language does not exists
* @throws XWikiException error when loading the document
* @deprecated since 4.3M2 use {@link #getTranslatedDocument(Locale, XWikiContext)} insead
*/
@Deprecated
public XWikiDocument getTranslatedDocument(String language, XWikiContext context) throws XWikiException
{
return getTranslatedDocument(LocaleUtils.toLocale(language, Locale.ROOT), context);
}
/**
* Return the document in the provided language.
* <p>
* This method return this if the provided language does not exists. See
*
* @param locale the locale of the document to return
* @param context the XWiki Context
* @return the document in the provided language or this if the provided language does not exists
* @throws XWikiException error when loading the document
*/
public XWikiDocument getTranslatedDocument(Locale locale, XWikiContext context) throws XWikiException
{
XWikiDocument tdoc = this;
if (locale != null && !locale.equals(Locale.ROOT) && !locale.equals(getDefaultLocale())) {
try {
tdoc = context.getWiki().getDocument(new DocumentReference(getDocumentReference(), locale), context);
if (tdoc.isNew()) {
tdoc = this;
}
} catch (Exception e) {
tdoc = this;
}
}
return tdoc;
}
/**
* @deprecated since 4.3M1 use {@link #getRealLocale()} instead
*/
@Deprecated
public String getRealLanguage(XWikiContext context) throws XWikiException
{
return getRealLanguage();
}
/**
* {@inheritDoc}
*
* @see org.xwiki.bridge.DocumentModelBridge#getRealLanguage()
* @deprecated since 4.3M1 use {@link #getRealLocale()} instead
*/
@Override
@Deprecated
public String getRealLanguage()
{
String lang = getLanguage();
if (lang.equals("")) {
return getDefaultLanguage();
} else {
return lang;
}
}
/**
* @return the actual locale of the document
*/
public Locale getRealLocale()
{
Locale locale = getLocale();
if (locale.equals(Locale.ROOT)) {
locale = getDefaultLocale();
}
return locale;
}
/**
* @deprecated since 5.1M2 use {@link #getTranslationLocales(XWikiContext)} instead
*/
@Deprecated
public List<String> getTranslationList(XWikiContext context) throws XWikiException
{
return getStore().getTranslationList(this, context);
}
/**
* The locales of the translation of this document (the default locale is not included).
*
* @param context the XWiki context
* @return the locales of the translations
* @throws XWikiException if retriving the translations from the database failed
*/
public List<Locale> getTranslationLocales(XWikiContext context) throws XWikiException
{
List<String> translations = getTranslationList(context);
List<Locale> locales = new ArrayList<Locale>(translations.size());
for (String translationString : translations) {
locales.add(LocaleUtils.toLocale(translationString));
}
return locales;
}
public List<Delta> getXMLDiff(XWikiDocument fromDoc, XWikiDocument toDoc, XWikiContext context)
throws XWikiException, DifferentiationFailedException
{
return getDeltas(
Diff.diff(ToString.stringToArray(fromDoc.toXML(context)), ToString.stringToArray(toDoc.toXML(context))));
}
public List<Delta> getContentDiff(XWikiDocument fromDoc, XWikiDocument toDoc, XWikiContext context)
throws XWikiException, DifferentiationFailedException
{
return getDeltas(
Diff.diff(ToString.stringToArray(fromDoc.getContent()), ToString.stringToArray(toDoc.getContent())));
}
public List<Delta> getContentDiff(String fromRev, String toRev, XWikiContext context)
throws XWikiException, DifferentiationFailedException
{
XWikiDocument fromDoc = context.getWiki().getDocument(this, fromRev, context);
XWikiDocument toDoc = context.getWiki().getDocument(this, toRev, context);
return getContentDiff(fromDoc, toDoc, context);
}
public List<Delta> getContentDiff(String fromRev, XWikiContext context)
throws XWikiException, DifferentiationFailedException
{
XWikiDocument revdoc = context.getWiki().getDocument(this, fromRev, context);
return getContentDiff(revdoc, this, context);
}
public List<Delta> getLastChanges(XWikiContext context) throws XWikiException, DifferentiationFailedException
{
Version version = getRCSVersion();
try {
String prev = getDocumentArchive(context).getPrevVersion(version).toString();
XWikiDocument prevDoc = context.getWiki().getDocument(this, prev, context);
return getDeltas(
Diff.diff(ToString.stringToArray(prevDoc.getContent()), ToString.stringToArray(getContent())));
} catch (Exception ex) {
LOGGER.debug("Exception getting differences from previous version: " + ex.getMessage());
}
return new ArrayList<Delta>();
}
public List<Delta> getRenderedContentDiff(XWikiDocument fromDoc, XWikiDocument toDoc, XWikiContext context)
throws XWikiException, DifferentiationFailedException
{
String originalContent = fromDoc.getRenderedContent(context);
String newContent = toDoc.getRenderedContent(context);
return getDeltas(Diff.diff(ToString.stringToArray(originalContent), ToString.stringToArray(newContent)));
}
public List<Delta> getRenderedContentDiff(String fromRev, String toRev, XWikiContext context)
throws XWikiException, DifferentiationFailedException
{
XWikiDocument fromDoc = context.getWiki().getDocument(this, fromRev, context);
XWikiDocument toDoc = context.getWiki().getDocument(this, toRev, context);
return getRenderedContentDiff(fromDoc, toDoc, context);
}
public List<Delta> getRenderedContentDiff(String fromRev, XWikiContext context)
throws XWikiException, DifferentiationFailedException
{
XWikiDocument revdoc = context.getWiki().getDocument(this, fromRev, context);
return getRenderedContentDiff(revdoc, this, context);
}
protected List<Delta> getDeltas(Revision rev)
{
List<Delta> list = new ArrayList<Delta>();
for (int i = 0; i < rev.size(); i++) {
list.add(rev.getDelta(i));
}
return list;
}
public List<MetaDataDiff> getMetaDataDiff(String fromRev, String toRev, XWikiContext context) throws XWikiException
{
XWikiDocument fromDoc = context.getWiki().getDocument(this, fromRev, context);
XWikiDocument toDoc = context.getWiki().getDocument(this, toRev, context);
return getMetaDataDiff(fromDoc, toDoc, context);
}
public List<MetaDataDiff> getMetaDataDiff(String fromRev, XWikiContext context) throws XWikiException
{
XWikiDocument revdoc = context.getWiki().getDocument(this, fromRev, context);
return getMetaDataDiff(revdoc, this, context);
}
public List<MetaDataDiff> getMetaDataDiff(XWikiDocument fromDoc, XWikiDocument toDoc, XWikiContext context)
throws XWikiException
{
List<MetaDataDiff> list = new ArrayList<MetaDataDiff>();
if (fromDoc == null || toDoc == null) {
return list;
}
if (!fromDoc.getTitle().equals(toDoc.getTitle())) {
list.add(new MetaDataDiff("title", fromDoc.getTitle(), toDoc.getTitle()));
}
if (ObjectUtils.notEqual(fromDoc.getRelativeParentReference(), toDoc.getRelativeParentReference())) {
list.add(new MetaDataDiff("parent", fromDoc.getParent(), toDoc.getParent()));
}
if (ObjectUtils.notEqual(fromDoc.getAuthorReference(), toDoc.getAuthorReference())) {
list.add(new MetaDataDiff("author", fromDoc.getAuthor(), toDoc.getAuthor()));
}
if (ObjectUtils.notEqual(fromDoc.getDocumentReference(), toDoc.getDocumentReference())) {
list.add(new MetaDataDiff("reference", fromDoc.getDocumentReference(), toDoc.getDocumentReference()));
}
if (!fromDoc.getSpace().equals(toDoc.getSpace())) {
list.add(new MetaDataDiff("web", fromDoc.getSpace(), toDoc.getSpace()));
}
if (!fromDoc.getName().equals(toDoc.getName())) {
list.add(new MetaDataDiff("name", fromDoc.getName(), toDoc.getName()));
}
if (ObjectUtils.notEqual(fromDoc.getLocale(), toDoc.getLocale())) {
list.add(new MetaDataDiff("language", fromDoc.getLanguage(), toDoc.getLanguage()));
}
if (ObjectUtils.notEqual(fromDoc.getDefaultLocale(), toDoc.getDefaultLocale())) {
list.add(new MetaDataDiff("defaultLanguage", fromDoc.getDefaultLanguage(), toDoc.getDefaultLanguage()));
}
if (ObjectUtils.notEqual(fromDoc.getSyntax(), toDoc.getSyntax())) {
list.add(new MetaDataDiff("syntax", fromDoc.getSyntax(), toDoc.getSyntax()));
}
if (fromDoc.isHidden() != toDoc.isHidden()) {
list.add(new MetaDataDiff("hidden", fromDoc.isHidden(), toDoc.isHidden()));
}
return list;
}
public List<List<ObjectDiff>> getObjectDiff(String fromRev, String toRev, XWikiContext context)
throws XWikiException
{
XWikiDocument fromDoc = context.getWiki().getDocument(this, fromRev, context);
XWikiDocument toDoc = context.getWiki().getDocument(this, toRev, context);
return getObjectDiff(fromDoc, toDoc, context);
}
public List<List<ObjectDiff>> getObjectDiff(String fromRev, XWikiContext context) throws XWikiException
{
XWikiDocument revdoc = context.getWiki().getDocument(this, fromRev, context);
return getObjectDiff(revdoc, this, context);
}
/**
* Return the object differences between two document versions. There is no hard requirement on the order of the two
* versions, but the results are semantically correct only if the two versions are given in the right order.
*
* @param fromDoc The old ('before') version of the document.
* @param toDoc The new ('after') version of the document.
* @param context The {@link com.xpn.xwiki.XWikiContext context}.
* @return The object differences. The returned list's elements are other lists, one for each changed object. The
* inner lists contain {@link ObjectDiff} elements, one object for each changed property of the object.
* Additionally, if the object was added or removed, then the first entry in the list will be an
* "object-added" or "object-removed" marker.
*/
public List<List<ObjectDiff>> getObjectDiff(XWikiDocument fromDoc, XWikiDocument toDoc, XWikiContext context)
{
List<List<ObjectDiff>> difflist = new ArrayList<List<ObjectDiff>>();
// Since objects could have been deleted or added, we iterate on both the old and the new
// object collections.
// First, iterate over the old objects.
for (List<BaseObject> objects : fromDoc.getXObjects().values()) {
for (BaseObject originalObj : objects) {
// This happens when objects are deleted, and the document is still in the cache
// storage.
if (originalObj != null) {
BaseObject newObj = toDoc.getXObject(originalObj.getXClassReference(), originalObj.getNumber());
List<ObjectDiff> dlist;
if (newObj == null) {
// The object was deleted.
dlist = new BaseObject().getDiff(originalObj, context);
ObjectDiff deleteMarker =
new ObjectDiff(originalObj.getXClassReference(), originalObj.getNumber(),
originalObj.getGuid(), ObjectDiff.ACTION_OBJECTREMOVED, "", "", "", "");
dlist.add(0, deleteMarker);
} else {
// The object exists in both versions, but might have been changed.
dlist = newObj.getDiff(originalObj, context);
}
if (!dlist.isEmpty()) {
difflist.add(dlist);
}
}
}
}
// Second, iterate over the objects which are only in the new version.
for (List<BaseObject> objects : toDoc.getXObjects().values()) {
for (BaseObject newObj : objects) {
// This happens when objects are deleted, and the document is still in the cache
// storage.
if (newObj != null) {
BaseObject originalObj = fromDoc.getXObject(newObj.getXClassReference(), newObj.getNumber());
if (originalObj == null) {
// TODO: Refactor this so that getDiff() accepts null Object as input.
// Only consider added objects, the other case was treated above.
originalObj = new BaseObject();
originalObj.setXClassReference(newObj.getRelativeXClassReference());
originalObj.setNumber(newObj.getNumber());
originalObj.setGuid(newObj.getGuid());
List<ObjectDiff> dlist = newObj.getDiff(originalObj, context);
ObjectDiff addMarker = new ObjectDiff(newObj.getXClassReference(), newObj.getNumber(),
newObj.getGuid(), ObjectDiff.ACTION_OBJECTADDED, "", "", "", "");
dlist.add(0, addMarker);
if (!dlist.isEmpty()) {
difflist.add(dlist);
}
}
}
}
}
return difflist;
}
public List<List<ObjectDiff>> getClassDiff(XWikiDocument fromDoc, XWikiDocument toDoc, XWikiContext context)
{
List<List<ObjectDiff>> difflist = new ArrayList<List<ObjectDiff>>();
BaseClass oldClass = fromDoc.getXClass();
BaseClass newClass = toDoc.getXClass();
if ((newClass == null) && (oldClass == null)) {
return difflist;
}
List<ObjectDiff> dlist = newClass.getDiff(oldClass, context);
if (!dlist.isEmpty()) {
difflist.add(dlist);
}
return difflist;
}
/**
* @param fromDoc
* @param toDoc
* @param context
* @return
*/
public List<AttachmentDiff> getAttachmentDiff(XWikiDocument fromDoc, XWikiDocument toDoc, XWikiContext context)
{
List<AttachmentDiff> difflist = new ArrayList<AttachmentDiff>();
for (XWikiAttachment origAttach : fromDoc.getAttachmentList()) {
String fileName = origAttach.getFilename();
XWikiAttachment newAttach = toDoc.getAttachment(fileName);
if (newAttach == null) {
difflist.add(new AttachmentDiff(fileName, org.xwiki.diff.Delta.Type.DELETE, origAttach, newAttach));
} else {
try {
if (!origAttach.equalsData(newAttach, context)) {
difflist
.add(new AttachmentDiff(fileName, org.xwiki.diff.Delta.Type.CHANGE, origAttach, newAttach));
}
} catch (XWikiException e) {
LOGGER.error("Failed to compare attachments [{}] and [{}]", origAttach.getReference(),
newAttach.getReference(), e);
}
}
}
for (XWikiAttachment newAttach : toDoc.getAttachmentList()) {
String fileName = newAttach.getFilename();
XWikiAttachment origAttach = fromDoc.getAttachment(fileName);
if (origAttach == null) {
difflist.add(new AttachmentDiff(fileName, org.xwiki.diff.Delta.Type.INSERT, origAttach, newAttach));
}
}
return difflist;
}
/**
* Rename the current document and all the backlinks leading to it. Will also change parent field in all documents
* which list the document we are renaming as their parent.
* <p>
* See {@link #rename(String, java.util.List, com.xpn.xwiki.XWikiContext)} for more details.
*
* @param newDocumentReference the new document reference
* @param context the ubiquitous XWiki Context
* @throws XWikiException in case of an error
* @since 2.2M2
*/
public void rename(DocumentReference newDocumentReference, XWikiContext context) throws XWikiException
{
rename(newDocumentReference, getBackLinkedReferences(context), context);
}
/**
* @deprecated since 2.2M2 use {@link #rename(DocumentReference, XWikiContext)}
*/
@Deprecated
public void rename(String newDocumentName, XWikiContext context) throws XWikiException
{
rename(newDocumentName, getBackLinkedPages(context), context);
}
/**
* Rename the current document and all the links pointing to it in the list of passed backlink documents. The
* renaming algorithm takes into account the fact that there are several ways to write a link to a given page and
* all those forms need to be renamed. For example the following links all point to the same page:
* <ul>
* <li>[Page]</li>
* <li>[Page?param=1]</li>
* <li>[currentwiki:Page]</li>
* <li>[CurrentSpace.Page]</li>
* <li>[currentwiki:CurrentSpace.Page]</li>
* </ul>
* <p>
* Note: links without a space are renamed with the space added and all documents which have the document being
* renamed as parent have their parent field set to "currentwiki:CurrentSpace.Page".
* </p>
*
* @param newDocumentReference the new document reference
* @param backlinkDocumentReferences the list of references of documents to parse and for which links will be
* modified to point to the new document reference
* @param context the ubiquitous XWiki Context
* @throws XWikiException in case of an error
* @since 2.2M2
*/
public void rename(DocumentReference newDocumentReference, List<DocumentReference> backlinkDocumentReferences,
XWikiContext context) throws XWikiException
{
rename(newDocumentReference, backlinkDocumentReferences, getChildrenReferences(context), context);
}
/**
* @deprecated since 2.2M2 use {@link #rename(DocumentReference, java.util.List, com.xpn.xwiki.XWikiContext)}
*/
@Deprecated
public void rename(String newDocumentName, List<String> backlinkDocumentNames, XWikiContext context)
throws XWikiException
{
rename(newDocumentName, backlinkDocumentNames, getChildren(context), context);
}
/**
* Same as {@link #rename(DocumentReference, List, XWikiContext)} but the list of documents having the current
* document as their parent is passed in parameter.
*
* @param newDocumentReference the new document reference
* @param backlinkDocumentReferences the list of references of documents to parse and for which links will be
* modified to point to the new document reference
* @param childDocumentReferences the list of references of document whose parent field will be set to the new
* document reference
* @param context the ubiquitous XWiki Context
* @throws XWikiException in case of an error
* @since 2.2M2
*/
public void rename(DocumentReference newDocumentReference, List<DocumentReference> backlinkDocumentReferences,
List<DocumentReference> childDocumentReferences, XWikiContext context) throws XWikiException
{
// TODO: Do all this in a single DB transaction as otherwise the state will be unknown if
// something fails in the middle...
// TODO: Why do we verify if the document has just been created and not been saved.
// If the user is trying to rename to the same name... In that case, simply exits for efficiency.
if (isNew() || getDocumentReference().equals(newDocumentReference)) {
return;
}
// Grab the xwiki object, it gets used a few times.
XWiki xwiki = context.getWiki();
// Step 1: Copy the document and all its translations under a new document with the new reference.
xwiki.copyDocument(getDocumentReference(), newDocumentReference, false, context);
// Step 2: For each child document, update its parent reference.
if (childDocumentReferences != null) {
for (DocumentReference childDocumentReference : childDocumentReferences) {
XWikiDocument childDocument = xwiki.getDocument(childDocumentReference, context);
String compactReference = getCompactEntityReferenceSerializer().serialize(newDocumentReference);
childDocument.setParent(compactReference);
String saveMessage = localizePlainOrKey("core.comment.renameParent", compactReference);
childDocument.setAuthorReference(context.getUserReference());
xwiki.saveDocument(childDocument, saveMessage, true, context);
}
}
// Step 3: For each backlink to rename, parse the backlink document and replace the links with the new name.
for (DocumentReference backlinkDocumentReference : backlinkDocumentReferences) {
XWikiDocument backlinkRootDocument = xwiki.getDocument(backlinkDocumentReference, context);
// Update default locale instance
renameLinks(backlinkRootDocument, getDocumentReference(), newDocumentReference, context);
// Update translations
for (Locale locale : backlinkRootDocument.getTranslationLocales(context)) {
XWikiDocument backlinkDocument = backlinkRootDocument.getTranslatedDocument(locale, context);
renameLinks(backlinkDocument, getDocumentReference(), newDocumentReference, context);
}
}
// Get new document
XWikiDocument newDocument = xwiki.getDocument(newDocumentReference, context);
// Step 4: Refactor the relative links contained in the document to make sure they are relative to the new
// document's location.
if (Utils.getContextComponentManager().hasComponent(BlockRenderer.class, getSyntax().toIdString())) {
// Only support syntax for which a renderer is provided
LinkedResourceHelper linkedResourceHelper = Utils.getComponent(LinkedResourceHelper.class);
DocumentReference oldDocumentReference = getDocumentReference();
XDOM newDocumentXDOM = newDocument.getXDOM();
List<Block> blocks = linkedResourceHelper.getBlocks(newDocumentXDOM);
// FIXME: Duplicate code. See org.xwiki.refactoring.internal.DefaultLinkRefactoring#updateRelativeLinks in
// xwiki-platform-refactoring-default
boolean modified = false;
for (Block block : blocks) {
ResourceReference resourceReference = linkedResourceHelper.getResourceReference(block);
if (resourceReference == null) {
// Skip invalid blocks.
continue;
}
ResourceType resourceType = resourceReference.getType();
// TODO: support ATTACHMENT as well.
if (!ResourceType.DOCUMENT.equals(resourceType) && !ResourceType.SPACE.equals(resourceType)) {
// We are currently only interested in Document or Space references.
continue;
}
// current link, use the old document's reference to fill in blanks.
EntityReference oldLinkReference = getResourceReferenceEntityReferenceResolver()
.resolve(resourceReference, null, oldDocumentReference);
// new link, use the new document's reference to fill in blanks.
EntityReference newLinkReference = getResourceReferenceEntityReferenceResolver()
.resolve(resourceReference, null, newDocumentReference);
// If the new and old link references don`t match, then we must update the relative link.
if (!newLinkReference.equals(oldLinkReference)) {
modified = true;
// Serialize the old (original) link relative to the new document's location, in compact form.
String serializedLinkReference =
getCompactWikiEntityReferenceSerializer().serialize(oldLinkReference, newDocumentReference);
// Update the reference in the XDOM.
linkedResourceHelper.setResourceReferenceString(block, serializedLinkReference);
}
}
// Set the new content and save document if needed
if (modified) {
newDocument.setContent(newDocumentXDOM);
newDocument.setAuthorReference(context.getUserReference());
xwiki.saveDocument(newDocument, context);
}
}
// Step 5: Delete the old document
xwiki.deleteDocument(this, context);
// Step 6: The current document needs to point to the renamed document as otherwise it's pointing to an
// invalid XWikiDocument object as it's been deleted...
clone(newDocument);
}
/**
* Rename links in passed document and save it if needed.
*/
private void renameLinks(XWikiDocument backlinkDocument, DocumentReference oldLink, DocumentReference newLink,
XWikiContext context) throws XWikiException
{
// FIXME: Duplicate code. See org.xwiki.refactoring.internal.DefaultLinkRefactoring#renameLinks in
// xwiki-platform-refactoring-default
getOldRendering().renameLinks(backlinkDocument, oldLink, newLink, context);
// Save if content changed
if (backlinkDocument.isContentDirty()) {
String saveMessage =
localizePlainOrKey("core.comment.renameLink", getCompactEntityReferenceSerializer().serialize(newLink));
backlinkDocument.setAuthorReference(context.getUserReference());
context.getWiki().saveDocument(backlinkDocument, saveMessage, true, context);
}
}
/**
* @deprecated since 2.2M2 use {@link #rename(DocumentReference, List, List, com.xpn.xwiki.XWikiContext)}
*/
@Deprecated
public void rename(String newDocumentName, List<String> backlinkDocumentNames, List<String> childDocumentNames,
XWikiContext context) throws XWikiException
{
List<DocumentReference> backlinkDocumentReferences = new ArrayList<DocumentReference>();
for (String backlinkDocumentName : backlinkDocumentNames) {
backlinkDocumentReferences.add(getCurrentMixedDocumentReferenceResolver().resolve(backlinkDocumentName));
}
List<DocumentReference> childDocumentReferences = new ArrayList<DocumentReference>();
for (String childDocumentName : childDocumentNames) {
childDocumentReferences.add(getCurrentMixedDocumentReferenceResolver().resolve(childDocumentName));
}
rename(getCurrentMixedDocumentReferenceResolver().resolve(newDocumentName), backlinkDocumentReferences,
childDocumentReferences, context);
}
/**
* @since 2.2M1
*/
public XWikiDocument copyDocument(DocumentReference newDocumentReference, XWikiContext context)
throws XWikiException
{
loadAttachments(context);
loadArchive(context);
XWikiDocument newdoc = duplicate(newDocumentReference);
// If the copied document has a title set to the original page name then set the new title to be the new page
// name.
if (StringUtils.equals(newdoc.getTitle(), getPrettyName(this.getDocumentReference()))) {
newdoc.setTitle(getPrettyName(newDocumentReference));
}
newdoc.setOriginalDocument(null);
newdoc.setContentDirty(true);
newdoc.getXClass().setOwnerDocument(newdoc);
XWikiDocumentArchive archive = getDocumentArchive();
if (archive != null) {
newdoc.setDocumentArchive(archive.clone(newdoc.getId(), context));
}
return newdoc;
}
/**
* Avoid the technical "WebHome" name.
*
* @param documentReference a document reference
* @return the last space name if the document is the home of a space, the document name otherwise
*/
private String getPrettyName(DocumentReference documentReference)
{
EntityReferenceProvider defaultEntityReferenceProvider = Utils.getComponent(EntityReferenceProvider.class);
if (defaultEntityReferenceProvider.getDefaultReference(documentReference.getType()).getName()
.equals(documentReference.getName())) {
return documentReference.getLastSpaceReference().getName();
}
return documentReference.getName();
}
/**
* @deprecated since 2.2M1 use {@link #copyDocument(DocumentReference, XWikiContext)} instead
*/
@Deprecated
public XWikiDocument copyDocument(String newDocumentName, XWikiContext context) throws XWikiException
{
return copyDocument(getCurrentMixedDocumentReferenceResolver().resolve(newDocumentName), context);
}
public XWikiLock getLock(XWikiContext context) throws XWikiException
{
XWikiLock theLock = getStore(context).loadLock(getId(), context, true);
if (theLock != null) {
int timeout = context.getWiki().getXWikiPreferenceAsInt("lock_Timeout", 30 * 60, context);
if (theLock.getDate().getTime() + timeout * 1000 < new Date().getTime()) {
getStore(context).deleteLock(theLock, context, true);
theLock = null;
}
}
return theLock;
}
public void setLock(String userName, XWikiContext context) throws XWikiException
{
XWikiLock lock = new XWikiLock(getId(), userName);
getStore(context).saveLock(lock, context, true);
}
public void removeLock(XWikiContext context) throws XWikiException
{
XWikiLock lock = getStore(context).loadLock(getId(), context, true);
if (lock != null) {
getStore(context).deleteLock(lock, context, true);
}
}
public void insertText(String text, String marker, XWikiContext context) throws XWikiException
{
setContent(StringUtils.replaceOnce(getContent(), marker, text + marker));
context.getWiki().saveDocument(this, context);
}
public Object getWikiNode()
{
return this.wikiNode;
}
public void setWikiNode(Object wikiNode)
{
this.wikiNode = wikiNode;
}
/**
* @since 2.2M1
*/
public String getXClassXML()
{
return this.xClassXML;
}
/**
* @deprecated since 2.2M1 use {@link #getXClassXML()} instead Hibernate uses this through reflection. It cannot be
* removed without altering hibernate.cfg.xml
*/
@Deprecated
public String getxWikiClassXML()
{
return getXClassXML();
}
/**
* @since 2.2M1
*/
public void setXClassXML(String xClassXML)
{
this.xClassXML = xClassXML;
}
/**
* @deprecated since 2.2M1 use {@link #setXClassXML(String)} ()} instead Hibernate uses this through reflection. It
* cannot be removed without altering hibernate.cfg.xml
*/
@Deprecated
public void setxWikiClassXML(String xClassXML)
{
setXClassXML(xClassXML);
}
public int getElements()
{
return this.elements;
}
public void setElements(int elements)
{
this.elements = elements;
}
public void setElement(int element, boolean toggle)
{
if (toggle) {
this.elements = this.elements | element;
} else {
this.elements = this.elements & (~element);
}
}
public boolean hasElement(int element)
{
return ((this.elements & element) == element);
}
/**
* Gets the default edit mode for this document. An edit mode (other than the default "edit") can be enforced by
* creating an {@code XWiki.EditModeClass} object in the current document, with the appropriate value for the
* defaultEditMode property, or by adding this object in a sheet included by the document. This function also falls
* back on the old {@code SheetClass}, deprecated since 3.1M2, which can be attached to included documents to
* specify that the current document should be edited inline.
*
* @return the default edit mode for this document ("edit" or "inline" usually)
* @param context the context of the request for this document
* @throws XWikiException since XWiki 6.3M1 it's not used anymore and "edit" is returned in case of error, with an
* error log
*/
public String getDefaultEditMode(XWikiContext context) throws XWikiException
{
try {
return getDefaultEditModeInternal(context);
} catch (Exception e) {
// If an error happens then we default to the "edit" mode. We don't want to fail by throwing an exception
// since it'll lead to several errors in the UI (such as when evaluating contentview.vm for example).
LOGGER.error("Failed to get the default edit mode for [{}]", getDocumentReference(), e);
return "edit";
}
}
private String getDefaultEditModeInternal(XWikiContext context) throws XWikiException
{
String editModeProperty = "defaultEditMode";
DocumentReference editModeClass =
getCurrentReferenceDocumentReferenceResolver().resolve(XWikiConstant.EDIT_MODE_CLASS);
// check if the current document has any edit mode class object attached to it, and read the edit mode from it
BaseObject editModeObject = this.getXObject(editModeClass);
if (editModeObject != null) {
String defaultEditMode = editModeObject.getStringValue(editModeProperty);
if (StringUtils.isEmpty(defaultEditMode)) {
return "edit";
} else {
return defaultEditMode;
}
}
// otherwise look for included documents
com.xpn.xwiki.XWiki xwiki = context.getWiki();
if (is10Syntax()) {
if (getContent().indexOf("includeForm(") != -1) {
return "inline";
}
} else {
// Algorithm: look in all include macros and for all document included check if one of them
// has an EditModeClass object attached to it, or a SheetClass object (deprecated since 3.1M2) attached to
// it. If so then the edit mode is inline.
// Find all include macros and extract the document names
// TODO: Is there a good way not to hardcode the macro name? The macro itself shouldn't know
// its own name since it's a deployment time concern.
for (Block macroBlock : getXDOM().getBlocks(new MacroBlockMatcher("include"), Axes.CHILD)) {
// Find the document reference to include by checking the macro's "reference" parameter.
// For backward-compatibility we also check for a "document" parameter since this is the parameter name
// that was used prior to XWiki 3.4M1 when the "reference" one was introduced and thus when the
// "document" one was deprecated.
String includedDocumentReference = macroBlock.getParameter("reference");
if (includedDocumentReference == null) {
includedDocumentReference = macroBlock.getParameter("document");
}
if (includedDocumentReference != null) {
// Resolve the document name into a valid Reference
DocumentReference documentReference =
getCurrentMixedDocumentReferenceResolver().resolve(includedDocumentReference);
XWikiDocument includedDocument = xwiki.getDocument(documentReference, context);
if (!includedDocument.isNew()) {
// get the edit mode object, first the new class and then the deprecated class if new class
// is not found
editModeObject = includedDocument.getXObject(editModeClass);
if (editModeObject == null) {
editModeObject = includedDocument.getXObject(SHEETCLASS_REFERENCE);
}
if (editModeObject != null) {
// Use the user-defined default edit mode if set.
String defaultEditMode = editModeObject.getStringValue(editModeProperty);
if (StringUtils.isBlank(defaultEditMode)) {
// TODO: maybe here the real value should be returned if the object is edit mode class,
// and inline only if the object is sheetclass
return "inline";
} else {
return defaultEditMode;
}
}
}
}
}
}
return "edit";
}
public String getDefaultEditURL(XWikiContext context) throws XWikiException
{
String editMode = getDefaultEditMode(context);
if ("inline".equals(editMode)) {
return getEditURL("inline", "", context);
} else {
com.xpn.xwiki.XWiki xwiki = context.getWiki();
String editor = xwiki.getEditorPreference(context);
return getEditURL("edit", editor, context);
}
}
public String getEditURL(String action, String mode, XWikiContext context) throws XWikiException
{
com.xpn.xwiki.XWiki xwiki = context.getWiki();
String language = "";
XWikiDocument tdoc = (XWikiDocument) context.get("tdoc");
String realLang = tdoc.getRealLanguage(context);
if ((xwiki.isMultiLingual(context) == true) && (!realLang.equals(""))) {
language = realLang;
}
return getEditURL(action, mode, language, context);
}
public String getEditURL(String action, String mode, String language, XWikiContext context)
{
StringBuilder editparams = new StringBuilder();
if (!mode.equals("")) {
editparams.append("xpage=");
editparams.append(mode);
}
if (!language.equals("")) {
if (!mode.equals("")) {
editparams.append("&");
}
editparams.append("language=");
editparams.append(language);
}
return getURL(action, editparams.toString(), context);
}
public String getDefaultTemplate()
{
if (this.defaultTemplate == null) {
return "";
} else {
return this.defaultTemplate;
}
}
public void setDefaultTemplate(String defaultTemplate)
{
this.defaultTemplate = defaultTemplate;
setMetaDataDirty(true);
}
public Vector<BaseObject> getComments()
{
return getComments(true);
}
/**
* @return the syntax of the document
* @since 2.3M1
*/
@Override
public Syntax getSyntax()
{
// Can't be initialized in the XWikiDocument constructor because #getDefaultDocumentSyntax() need to create a
// XWikiDocument object to get preferences from wiki preferences pages and would thus generate an infinite loop
if (isNew() && this.syntax == null) {
this.syntax = getDefaultDocumentSyntax();
}
return this.syntax;
}
/**
* {@inheritDoc}
* <p>
* Note that this method cannot be removed for now since it's used by Hibernate for saving a XWikiDocument.
* </p>
*
* @see org.xwiki.bridge.DocumentModelBridge#getSyntaxId()
* @deprecated since 2.3M1, use {link #getSyntax()} instead
*/
@Override
@Deprecated
public String getSyntaxId()
{
return getSyntax().toIdString();
}
/**
* @param syntax the new syntax to set for this document
* @see #getSyntax()
* @since 2.3M1
*/
public void setSyntax(Syntax syntax)
{
if (ObjectUtils.notEqual(this.syntax, syntax)) {
this.syntax = syntax;
// invalidate parsed xdom
this.xdomCache = null;
}
}
/**
* Note that this method cannot be removed for now since it's used by Hibernate for saving a XWikiDocument.
*
* @param syntaxId the new syntax id to set (e.g. {@code xwiki/2.0}, {@code xwiki/2.1}, etc)
* @see #getSyntaxId()
* @deprecated since 2.3M1, use {link #setSyntax(Syntax)} instead
*/
@Deprecated
public void setSyntaxId(String syntaxId)
{
Syntax syntax;
// In order to preserve backward-compatibility with previous versions of XWiki in which the notion of Syntax Id
// did not exist, we check the passed syntaxId parameter. Since this parameter comes from the database (it's
// called automatically by Hibernate) it can be NULL or empty. In this case we consider the document is in
// syntax/1.0 syntax.
if (StringUtils.isBlank(syntaxId)) {
syntax = Syntax.XWIKI_1_0;
} else {
try {
syntax = getSyntaxFactory().createSyntaxFromIdString(syntaxId);
} catch (ParseException e) {
syntax = getDefaultDocumentSyntax();
LOGGER.warn("Failed to set syntax [" + syntaxId + "] for ["
+ getDefaultEntityReferenceSerializer().serialize(getDocumentReference()) + "], setting syntax ["
+ syntax.toIdString() + "] instead.", e);
}
}
setSyntax(syntax);
}
public Vector<BaseObject> getComments(boolean asc)
{
List<BaseObject> list = getXObjects(COMMENTSCLASS_REFERENCE);
if (list == null) {
return null;
} else if (asc) {
return new Vector<BaseObject>(list);
} else {
Vector<BaseObject> newlist = new Vector<BaseObject>();
for (int i = list.size() - 1; i >= 0; i--) {
newlist.add(list.get(i));
}
return newlist;
}
}
public boolean isCurrentUserCreator(XWikiContext context)
{
return isCreator(context.getUserReference());
}
/**
* @deprecated use {@link #isCreator(DocumentReference)} instead
*/
@Deprecated
public boolean isCreator(String username)
{
if (username.equals(XWikiRightService.GUEST_USER_FULLNAME)) {
return false;
}
return username.equals(getCreator());
}
public boolean isCreator(DocumentReference username)
{
if (username == null) {
return false;
}
return username.equals(getCreatorReference());
}
public boolean isCurrentUserPage(XWikiContext context)
{
DocumentReference userReference = context.getUserReference();
if (userReference == null) {
return false;
}
return userReference.equals(getDocumentReference());
}
public boolean isCurrentLocalUserPage(XWikiContext context)
{
final DocumentReference userRef = context.getUserReference();
return userRef != null && userRef.equals(this.getDocumentReference());
}
public void resetArchive(XWikiContext context) throws XWikiException
{
boolean hasVersioning = context.getWiki().hasVersioning(context);
if (hasVersioning) {
getVersioningStore(context).resetRCSArchive(this, true, context);
}
}
/**
* Adds an object from an new object creation form.
*
* @since 2.2M2
*/
public BaseObject addXObjectFromRequest(XWikiContext context) throws XWikiException
{
// Read info in object
ObjectAddForm form = new ObjectAddForm();
form.setRequest(context.getRequest());
form.readRequest();
EntityReference classReference = getXClassEntityReferenceResolver().resolve(form.getClassName(),
EntityType.DOCUMENT, getDocumentReference());
BaseObject object = newXObject(classReference, context);
BaseClass baseclass = object.getXClass(context);
baseclass.fromMap(form.getObject(LOCAL_REFERENCE_SERIALIZER.serialize(resolveClassReference(classReference))),
object);
return object;
}
/**
* Adds an object from an new object creation form.
*
* @since 2.2.3
*/
public BaseObject addXObjectFromRequest(EntityReference classReference, XWikiContext context) throws XWikiException
{
return addXObjectFromRequest(classReference, "", 0, context);
}
/**
* @deprecated since 2.2M2 use {@link #addXObjectFromRequest(EntityReference, XWikiContext)}
*/
@Deprecated
public BaseObject addObjectFromRequest(String className, XWikiContext context) throws XWikiException
{
return addObjectFromRequest(className, "", 0, context);
}
/**
* Adds an object from an new object creation form.
*
* @since 2.2M2
*/
public BaseObject addXObjectFromRequest(DocumentReference classReference, String prefix, XWikiContext context)
throws XWikiException
{
return addXObjectFromRequest(classReference, prefix, 0, context);
}
/**
* @deprecated since 2.2M2 use {@link #addXObjectFromRequest(DocumentReference, String, XWikiContext)}
*/
@Deprecated
public BaseObject addObjectFromRequest(String className, String prefix, XWikiContext context) throws XWikiException
{
return addObjectFromRequest(className, prefix, 0, context);
}
/**
* Adds multiple objects from an new objects creation form.
*
* @since 2.2M2
*/
public List<BaseObject> addXObjectsFromRequest(DocumentReference classReference, XWikiContext context)
throws XWikiException
{
return addXObjectsFromRequest(classReference, "", context);
}
/**
* @deprecated since 2.2M2 use {@link #addXObjectsFromRequest(DocumentReference, XWikiContext)}
*/
@Deprecated
public List<BaseObject> addObjectsFromRequest(String className, XWikiContext context) throws XWikiException
{
return addObjectsFromRequest(className, "", context);
}
/**
* Adds multiple objects from an new objects creation form.
*
* @since 2.2M2
*/
public List<BaseObject> addXObjectsFromRequest(DocumentReference classReference, String pref, XWikiContext context)
throws XWikiException
{
@SuppressWarnings("unchecked")
Map<String, String[]> map = context.getRequest().getParameterMap();
List<Integer> objectsNumberDone = new ArrayList<Integer>();
List<BaseObject> objects = new ArrayList<BaseObject>();
String start = pref + LOCAL_REFERENCE_SERIALIZER.serialize(classReference) + "_";
for (String name : map.keySet()) {
if (name.startsWith(start)) {
int pos = name.indexOf('_', start.length() + 1);
String prefix = name.substring(0, pos);
int num = Integer.decode(prefix.substring(prefix.lastIndexOf('_') + 1)).intValue();
if (!objectsNumberDone.contains(Integer.valueOf(num))) {
objectsNumberDone.add(Integer.valueOf(num));
objects.add(addXObjectFromRequest(classReference, pref, num, context));
}
}
}
return objects;
}
/**
* @deprecated since 2.2M2 use {@link #addXObjectsFromRequest(DocumentReference, String, XWikiContext)}
*/
@Deprecated
public List<BaseObject> addObjectsFromRequest(String className, String pref, XWikiContext context)
throws XWikiException
{
return addXObjectsFromRequest(resolveClassReference(className), pref, context);
}
/**
* Adds object from an new object creation form.
*
* @since 2.2M2
*/
public BaseObject addXObjectFromRequest(DocumentReference classReference, int num, XWikiContext context)
throws XWikiException
{
return addXObjectFromRequest(classReference, "", num, context);
}
/**
* @deprecated since 2.2M2 use {@link #addXObjectFromRequest(DocumentReference, int, XWikiContext)}
*/
@Deprecated
public BaseObject addObjectFromRequest(String className, int num, XWikiContext context) throws XWikiException
{
return addObjectFromRequest(className, "", num, context);
}
/**
* Adds object from an new object creation form.
*
* @since 2.2.3
*/
public BaseObject addXObjectFromRequest(EntityReference classReference, String prefix, int num,
XWikiContext context) throws XWikiException
{
BaseObject object = newXObject(classReference, context);
BaseClass baseclass = object.getXClass(context);
String newPrefix =
prefix + LOCAL_REFERENCE_SERIALIZER.serialize(resolveClassReference(classReference)) + "_" + num;
baseclass.fromMap(Util.getObject(context.getRequest(), newPrefix), object);
return object;
}
/**
* @deprecated since 2.2M2 use {@link #addXObjectFromRequest(EntityReference, String, int, XWikiContext)}
*/
@Deprecated
public BaseObject addObjectFromRequest(String className, String prefix, int num, XWikiContext context)
throws XWikiException
{
return addXObjectFromRequest(resolveClassReference(className), prefix, num, context);
}
/**
* Adds an object from an new object creation form.
*
* @since 2.2.3
*/
public BaseObject updateXObjectFromRequest(EntityReference classReference, XWikiContext context)
throws XWikiException
{
return updateXObjectFromRequest(classReference, "", context);
}
/**
* @deprecated since 2.2M2 use {@link #updateXObjectFromRequest(EntityReference, XWikiContext)}
*/
@Deprecated
public BaseObject updateObjectFromRequest(String className, XWikiContext context) throws XWikiException
{
return updateObjectFromRequest(className, "", context);
}
/**
* Adds an object from an new object creation form.
*
* @since 2.2.3
*/
public BaseObject updateXObjectFromRequest(EntityReference classReference, String prefix, XWikiContext context)
throws XWikiException
{
return updateXObjectFromRequest(classReference, prefix, 0, context);
}
/**
* @deprecated since 2.2M2 use {@link #updateXObjectFromRequest(EntityReference, String, XWikiContext)}
*/
@Deprecated
public BaseObject updateObjectFromRequest(String className, String prefix, XWikiContext context)
throws XWikiException
{
return updateObjectFromRequest(className, prefix, 0, context);
}
/**
* Adds an object from an new object creation form.
*
* @since 2.2.3
*/
public BaseObject updateXObjectFromRequest(EntityReference classReference, String prefix, int num,
XWikiContext context) throws XWikiException
{
DocumentReference absoluteClassReference = resolveClassReference(classReference);
int nb;
BaseObject oldobject = getXObject(absoluteClassReference, num);
if (oldobject == null) {
nb = createXObject(classReference, context);
oldobject = getXObject(absoluteClassReference, nb);
} else {
nb = oldobject.getNumber();
}
BaseClass baseclass = oldobject.getXClass(context);
String newPrefix = prefix + LOCAL_REFERENCE_SERIALIZER.serialize(absoluteClassReference) + "_" + nb;
BaseObject newobject =
(BaseObject) baseclass.fromMap(Util.getObject(context.getRequest(), newPrefix), oldobject);
newobject.setNumber(oldobject.getNumber());
newobject.setGuid(oldobject.getGuid());
setXObject(nb, newobject);
return newobject;
}
/**
* @deprecated since 2.2M2 use {@link #updateXObjectFromRequest(EntityReference, String, int, XWikiContext)}
*/
@Deprecated
public BaseObject updateObjectFromRequest(String className, String prefix, int num, XWikiContext context)
throws XWikiException
{
return updateXObjectFromRequest(
getXClassEntityReferenceResolver().resolve(className, EntityType.DOCUMENT, getDocumentReference()), prefix,
num, context);
}
/**
* Adds an object from an new object creation form.
*
* @since 2.2.3
*/
public List<BaseObject> updateXObjectsFromRequest(EntityReference classReference, XWikiContext context)
throws XWikiException
{
return updateXObjectsFromRequest(classReference, "", context);
}
/**
* @deprecated since 2.2M2 use {@link #updateXObjectsFromRequest(EntityReference, XWikiContext)}
*/
@Deprecated
public List<BaseObject> updateObjectsFromRequest(String className, XWikiContext context) throws XWikiException
{
return updateObjectsFromRequest(className, "", context);
}
/**
* Adds multiple objects from an new objects creation form.
*
* @since 2.2.3
*/
public List<BaseObject> updateXObjectsFromRequest(EntityReference classReference, String pref, XWikiContext context)
throws XWikiException
{
DocumentReference absoluteClassReference = resolveClassReference(classReference);
@SuppressWarnings("unchecked")
Map<String, String[]> map = context.getRequest().getParameterMap();
List<Integer> objectsNumberDone = new ArrayList<Integer>();
List<BaseObject> objects = new ArrayList<BaseObject>();
String start = pref + LOCAL_REFERENCE_SERIALIZER.serialize(absoluteClassReference) + "_";
for (String name : map.keySet()) {
if (name.startsWith(start)) {
int pos = name.indexOf('_', start.length() + 1);
String prefix = name.substring(0, pos);
int num = Integer.decode(prefix.substring(prefix.lastIndexOf('_') + 1)).intValue();
if (!objectsNumberDone.contains(Integer.valueOf(num))) {
objectsNumberDone.add(Integer.valueOf(num));
objects.add(updateXObjectFromRequest(classReference, pref, num, context));
}
}
}
return objects;
}
/**
* @deprecated since 2.2M2 use {@link #updateXObjectsFromRequest(EntityReference, String, XWikiContext)}
*/
@Deprecated
public List<BaseObject> updateObjectsFromRequest(String className, String pref, XWikiContext context)
throws XWikiException
{
return updateXObjectsFromRequest(
getXClassEntityReferenceResolver().resolve(className, EntityType.DOCUMENT, getDocumentReference()), pref,
context);
}
public boolean isAdvancedContent()
{
String[] matches = { "<%", "#set", "#include", "#if", "public class", "/* Advanced content */",
"## Advanced content", "/* Programmatic content */", "## Programmatic content" };
String content2 = getContent().toLowerCase();
for (String match : matches) {
if (content2.indexOf(match.toLowerCase()) != -1) {
return true;
}
}
if (HTML_TAG_PATTERN.matcher(content2).find()) {
return true;
}
return false;
}
public boolean isProgrammaticContent()
{
String[] matches = { "<%", "\\$xwiki.xWiki", "$xcontext.context", "$doc.document", "$xwiki.getXWiki()",
"$xcontext.getContext()", "$doc.getDocument()", "WithProgrammingRights(", "/* Programmatic content */",
"## Programmatic content", "$xwiki.search(", "$xwiki.createUser", "$xwiki.createNewWiki",
"$xwiki.addToAllGroup", "$xwiki.sendMessage", "$xwiki.copyDocument", "$xwiki.copyWikiWeb",
"$xwiki.copySpaceBetweenWikis", "$xwiki.parseGroovyFromString", "$doc.toXML()", "$doc.toXMLDocument()", };
String content2 = getContent().toLowerCase();
for (String match : matches) {
if (content2.indexOf(match.toLowerCase()) != -1) {
return true;
}
}
return false;
}
/**
* Remove an XObject from the document. The changes are not persisted until the document is saved.
*
* @param object the object to remove
* @return {@code true} if the object was successfully removed, {@code false} if the object was not found in the
* current document.
* @since 2.2M1
*/
public boolean removeXObject(BaseObject object)
{
List<BaseObject> objects = this.xObjects.get(object.getXClassReference());
// No objects at all, nothing to remove
if (objects == null) {
return false;
}
// Sometimes the object vector is wrongly indexed, meaning that objects are not at the right position
// Check if the right object is in place
int objectPosition = object.getNumber();
if (objectPosition < objects.size()) {
BaseObject storedObject = objects.get(objectPosition);
if (storedObject == null || !storedObject.equals(object)) {
// Try to find the correct position
objectPosition = objects.indexOf(object);
}
} else {
// The object position is greater than the array, that's invalid!
objectPosition = -1;
}
// If the object is not in the document, simply ignore this request
if (objectPosition < 0) {
return false;
}
// We don't remove objects, but set null in their place, so that the object number corresponds to its position
// in the vector
objects.set(objectPosition, null);
// Schedule the object for removal from the storage
addXObjectToRemove(object);
return true;
}
/**
* Remove an XObject from the document. The changes are not persisted until the document is saved.
*
* @param object the object to remove
* @return {@code true} if the object was successfully removed, {@code false} if the object was not found in the
* current document.
* @deprecated since 2.2M1, use {@link #removeXObject(com.xpn.xwiki.objects.BaseObject)} instead
*/
@Deprecated
public boolean removeObject(BaseObject object)
{
return removeXObject(object);
}
/**
* Remove all the objects of a given type (XClass) from the document. The object counter is left unchanged, so that
* future objects will have new (different) numbers. However, on some storage engines the counter will be reset if
* the document is removed from the cache and reloaded from the persistent storage.
*
* @param classReference The XClass reference of the XObjects to be removed.
* @return {@code true} if the objects were successfully removed, {@code false} if no object from the target class
* was in the current document.
* @since 2.2M1
*/
public boolean removeXObjects(DocumentReference classReference)
{
List<BaseObject> objects = this.xObjects.get(classReference);
// No objects at all, nothing to remove
if (objects == null) {
return false;
}
// Schedule the object for removal from the storage
for (BaseObject object : objects) {
if (object != null) {
addXObjectToRemove(object);
}
}
// Empty the vector, retaining its size
int currentSize = objects.size();
objects.clear();
for (int i = 0; i < currentSize; i++) {
objects.add(null);
}
return true;
}
/**
* Remove all the objects of a given type (XClass) from the document. The object counter is left unchanged, so that
* future objects will have new (different) numbers. However, on some storage engines the counter will be reset if
* the document is removed from the cache and reloaded from the persistent storage.
*
* @param reference The XClass reference of the XObjects to be removed.
* @return {@code true} if the objects were successfully removed, {@code false} if no object from the target class
* was in the current document.
* @since 5.0M1
*/
public boolean removeXObjects(EntityReference reference)
{
return removeXObjects(
getCurrentReferenceDocumentReferenceResolver().resolve(reference, getDocumentReference()));
}
/**
* Remove all the objects of a given type (XClass) from the document. The object counter is left unchanged, so that
* future objects will have new (different) numbers. However, on some storage engines the counter will be reset if
* the document is removed from the cache and reloaded from the persistent storage.
*
* @param className The class name of the objects to be removed.
* @return {@code true} if the objects were successfully removed, {@code false} if no object from the target class
* was in the current document.
* @deprecated since 2.2M1 use {@link #removeXObjects(org.xwiki.model.reference.DocumentReference)} instead
*/
@Deprecated
public boolean removeObjects(String className)
{
return removeXObjects(resolveClassReference(className));
}
/**
* Get the top sections contained in the document.
* <p>
* The section are filtered by xwiki.section.depth property on the maximum depth of the sections to return. This
* method is usually used to get "editable" sections.
*
* @return the sections in the current document
*/
public List<DocumentSection> getSections() throws XWikiException
{
if (is10Syntax()) {
return getSections10();
} else {
List<DocumentSection> splitSections = new ArrayList<DocumentSection>();
List<HeaderBlock> headers = getFilteredHeaders();
int sectionNumber = 1;
for (HeaderBlock header : headers) {
// put -1 as index since there is no way to get the position of the header in the source
int documentSectionIndex = -1;
// Need to do the same thing than 1.0 content here
String documentSectionLevel = StringUtils.repeat("1.", header.getLevel().getAsInt() - 1) + "1";
DocumentSection docSection = new DocumentSection(sectionNumber++, documentSectionIndex,
documentSectionLevel, renderXDOM(new XDOM(header.getChildren()), getSyntax()));
splitSections.add(docSection);
}
return splitSections;
}
}
/**
* Get XWiki context from execution context.
*
* @return the XWiki context for the current thread
*/
private XWikiContext getXWikiContext()
{
Provider<XWikiContext> xcontextProvider = Utils.getComponent(XWikiContext.TYPE_PROVIDER);
if (xcontextProvider != null) {
return xcontextProvider.get();
}
return null;
}
/**
* Filter the headers from a document XDOM based on xwiki.section.depth property from xwiki.cfg file.
*
* @return the filtered headers
*/
private List<HeaderBlock> getFilteredHeaders()
{
List<HeaderBlock> filteredHeaders = new ArrayList<HeaderBlock>();
// Get the maximum header level
int sectionDepth = 2;
XWikiContext context = getXWikiContext();
if (context != null) {
sectionDepth = (int) context.getWiki().getSectionEditingDepth();
}
// Get the headers.
//
// Note that we need to only take into account SectionBlock that are children of other SectionBlocks so that
// we are in sync with the section editing buttons added in xwiki.js. Being able to section edit any heading is
// too complex. For example if you have (in XWiki Syntax 2.0):
// = Heading1 =
// para1
// == Heading2 ==
// para2
// (((
// == Heading3 ==
// para3
// (((
// == Heading4 ==
// para4
// )))
// )))
// == Heading5 ==
// para5
//
// Then if we were to support editing "Heading4", its content would be:
// para4
// )))
// )))
//
// Which obviously is not correct...
final XDOM xdom = getXDOM();
if (!xdom.getChildren().isEmpty()) {
Block currentBlock = xdom.getChildren().get(0);
while (currentBlock != null) {
if (currentBlock instanceof SectionBlock) {
// The next children block is a HeaderBlock but we check to be on the safe side...
Block nextChildrenBlock = currentBlock.getChildren().get(0);
if (nextChildrenBlock instanceof HeaderBlock) {
HeaderBlock headerBlock = (HeaderBlock) nextChildrenBlock;
if (headerBlock.getLevel().getAsInt() <= sectionDepth) {
filteredHeaders.add(headerBlock);
}
}
currentBlock = nextChildrenBlock;
} else {
Block nextSibling = currentBlock.getNextSibling();
if (nextSibling == null) {
currentBlock = currentBlock.getParent();
while (currentBlock != null) {
if (currentBlock.getNextSibling() != null) {
currentBlock = currentBlock.getNextSibling();
break;
}
currentBlock = currentBlock.getParent();
}
} else {
currentBlock = nextSibling;
}
}
}
}
return filteredHeaders;
}
/**
* @return the sections in the current document
*/
private List<DocumentSection> getSections10()
{
// Pattern to match the title. Matches only level 1 and level 2 headings.
Pattern headingPattern = Pattern.compile("^[ \\t]*+(1(\\.1){0,1}+)[ \\t]++(.++)$", Pattern.MULTILINE);
Matcher matcher = headingPattern.matcher(getContent());
List<DocumentSection> splitSections = new ArrayList<DocumentSection>();
int sectionNumber = 0;
// find title to split
while (matcher.find()) {
++sectionNumber;
String sectionLevel = matcher.group(1);
String sectionTitle = matcher.group(3);
int sectionIndex = matcher.start();
// Initialize a documentSection object.
DocumentSection docSection = new DocumentSection(sectionNumber, sectionIndex, sectionLevel, sectionTitle);
// Add the document section to list.
splitSections.add(docSection);
}
return splitSections;
}
/**
* Return a Document section with parameter is sectionNumber.
*
* @param sectionNumber the index (+1) of the section in the list of all sections in the document.
* @return
* @throws XWikiException error when extracting sections from document
*/
public DocumentSection getDocumentSection(int sectionNumber) throws XWikiException
{
// return a document section according to section number
return getSections().get(sectionNumber - 1);
}
/**
* Return the content of a section.
*
* @param sectionNumber the index (+1) of the section in the list of all sections in the document.
* @return the content of a section or null if the section can't be found.
* @throws XWikiException error when trying to extract section content
*/
public String getContentOfSection(int sectionNumber) throws XWikiException
{
String content = null;
if (is10Syntax()) {
content = getContentOfSection10(sectionNumber);
} else {
List<HeaderBlock> headers = getFilteredHeaders();
if (headers.size() >= sectionNumber) {
SectionBlock section = headers.get(sectionNumber - 1).getSection();
content = renderXDOM(new XDOM(Collections.<Block>singletonList(section)), getSyntax());
}
}
return content;
}
/**
* Return the content of a section.
*
* @param sectionNumber the index (+1) of the section in the list of all sections in the document.
* @return the content of a section
* @throws XWikiException error when trying to extract section content
*/
private String getContentOfSection10(int sectionNumber) throws XWikiException
{
List<DocumentSection> splitSections = getSections();
int indexEnd = 0;
// get current section
DocumentSection section = splitSections.get(sectionNumber - 1);
int indexStart = section.getSectionIndex();
String sectionLevel = section.getSectionLevel();
// Determine where this section ends, which is at the start of the next section of the
// same or a higher level.
for (int i = sectionNumber; i < splitSections.size(); i++) {
DocumentSection nextSection = splitSections.get(i);
String nextLevel = nextSection.getSectionLevel();
if (sectionLevel.equals(nextLevel) || sectionLevel.length() > nextLevel.length()) {
indexEnd = nextSection.getSectionIndex();
break;
}
}
String sectionContent = null;
if (indexStart < 0) {
indexStart = 0;
}
if (indexEnd == 0) {
sectionContent = getContent().substring(indexStart);
} else {
sectionContent = getContent().substring(indexStart, indexEnd);
}
return sectionContent;
}
/**
* Update a section content in document.
*
* @param sectionNumber the index (starting at 1) of the section in the list of all sections in the document.
* @param newSectionContent the new section content.
* @return the new document content.
* @throws XWikiException error when updating content
*/
public String updateDocumentSection(int sectionNumber, String newSectionContent) throws XWikiException
{
String content;
if (is10Syntax()) {
content = updateDocumentSection10(sectionNumber, newSectionContent);
} else {
// Get the current section block
HeaderBlock header = getFilteredHeaders().get(sectionNumber - 1);
XDOM xdom = (XDOM) header.getRoot();
// newSectionContent -> Blocks
List<Block> blocks = parseContent(newSectionContent).getChildren();
int sectionLevel = header.getLevel().getAsInt();
for (int level = 1; level < sectionLevel && blocks.size() == 1
&& blocks.get(0) instanceof SectionBlock; ++level) {
blocks = blocks.get(0).getChildren();
}
// replace old current SectionBlock with new Blocks
Block section = header.getSection();
section.getParent().replaceChild(blocks, section);
// render back XDOM to document's content syntax
content = renderXDOM(xdom, getSyntax());
}
return content;
}
/**
* Update a section content in document.
*
* @param sectionNumber the index (+1) of the section in the list of all sections in the document.
* @param newSectionContent the new section content.
* @return the new document content.
* @throws XWikiException error when updating document content with section content
*/
private String updateDocumentSection10(int sectionNumber, String newSectionContent) throws XWikiException
{
StringBuilder newContent = new StringBuilder();
// get document section that will be edited
DocumentSection docSection = getDocumentSection(sectionNumber);
int numberOfSections = getSections().size();
int indexSection = docSection.getSectionIndex();
if (numberOfSections == 1) {
// there is only a sections in document
String contentBegin = getContent().substring(0, indexSection);
newContent = newContent.append(contentBegin).append(newSectionContent);
return newContent.toString();
} else if (sectionNumber == numberOfSections) {
// edit lastest section that doesn't contain subtitle
String contentBegin = getContent().substring(0, indexSection);
newContent = newContent.append(contentBegin).append(newSectionContent);
return newContent.toString();
} else {
String sectionLevel = docSection.getSectionLevel();
int nextSectionIndex = 0;
// get index of next section
for (int i = sectionNumber; i < numberOfSections; i++) {
DocumentSection nextSection = getDocumentSection(i + 1); // get next section
String nextSectionLevel = nextSection.getSectionLevel();
if (sectionLevel.equals(nextSectionLevel)) {
nextSectionIndex = nextSection.getSectionIndex();
break;
} else if (sectionLevel.length() > nextSectionLevel.length()) {
nextSectionIndex = nextSection.getSectionIndex();
break;
}
}
if (nextSectionIndex == 0) {// edit the last section
newContent = newContent.append(getContent().substring(0, indexSection)).append(newSectionContent);
return newContent.toString();
} else {
String contentAfter = getContent().substring(nextSectionIndex);
String contentBegin = getContent().substring(0, indexSection);
newContent = newContent.append(contentBegin).append(newSectionContent).append(contentAfter);
}
return newContent.toString();
}
}
/**
* Computes a document hash, taking into account all document data: content, objects, attachments, metadata... TODO:
* cache the hash value, update only on modification.
*/
public String getVersionHashCode(XWikiContext context)
{
MessageDigest md5 = null;
try {
md5 = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException ex) {
LOGGER.error("Cannot create MD5 object", ex);
return hashCode() + "";
}
try {
String valueBeforeMD5 = toXML(true, false, true, false, context);
md5.update(valueBeforeMD5.getBytes());
byte[] array = md5.digest();
StringBuilder sb = new StringBuilder();
for (byte element : array) {
int b = element & 0xFF;
if (b < 0x10) {
sb.append('0');
}
sb.append(Integer.toHexString(b));
}
return sb.toString();
} catch (Exception ex) {
LOGGER.error("Exception while computing document hash", ex);
}
return hashCode() + "";
}
public static String getInternalPropertyName(String propname, XWikiContext context)
{
ContextualLocalizationManager localizationManager = Utils.getComponent(ContextualLocalizationManager.class);
String cpropname = StringUtils.capitalize(propname);
return localizationManager == null ? cpropname : localizationManager.getTranslationPlain(cpropname);
}
public String getInternalProperty(String propname)
{
String methodName = "get" + StringUtils.capitalize(propname);
try {
Method method = getClass().getDeclaredMethod(methodName, (Class[]) null);
return (String) method.invoke(this, (Object[]) null);
} catch (Exception e) {
return null;
}
}
public String getCustomClass()
{
if (this.customClass == null) {
return "";
}
return this.customClass;
}
public void setCustomClass(String customClass)
{
this.customClass = customClass;
setMetaDataDirty(true);
}
public void setValidationScript(String validationScript)
{
this.validationScript = validationScript;
setMetaDataDirty(true);
}
public String getValidationScript()
{
if (this.validationScript == null) {
return "";
} else {
return this.validationScript;
}
}
public String getComment()
{
if (this.comment == null) {
return "";
}
return this.comment;
}
public void setComment(String comment)
{
this.comment = comment;
}
public boolean isMinorEdit()
{
return this.isMinorEdit;
}
public void setMinorEdit(boolean isMinor)
{
this.isMinorEdit = isMinor;
}
// methods for easy table update. It is need only for hibernate.
// when hibernate update old database without minorEdit field, hibernate will create field with
// null in despite of notnull in hbm.
// (http://opensource.atlassian.com/projects/hibernate/browse/HB-1151)
// so minorEdit will be null for old documents. But hibernate can't convert null to boolean.
// so we need convert Boolean to boolean
protected Boolean getMinorEdit1()
{
return Boolean.valueOf(isMinorEdit());
}
protected void setMinorEdit1(Boolean isMinor)
{
this.isMinorEdit = (isMinor != null && isMinor.booleanValue());
}
/**
* Create, add and return a new object with the provided class.
* <p>
* Note that absolute reference are not supported for xclasses which mean that the wiki part (whatever the wiki is)
* of the reference will be systematically removed.
*
* @param classReference the reference of the class
* @param context the XWiki context
* @return the newly created object
* @throws XWikiException error when creating the new object
* @since 2.2.3
*/
public BaseObject newXObject(EntityReference classReference, XWikiContext context) throws XWikiException
{
int nb = createXObject(classReference, context);
return getXObject(resolveClassReference(classReference), nb);
}
/**
* @deprecated since 2.2M2 use {@link #newXObject(EntityReference, XWikiContext)}
*/
@Deprecated
public BaseObject newObject(String className, XWikiContext context) throws XWikiException
{
return newXObject(
getXClassEntityReferenceResolver().resolve(className, EntityType.DOCUMENT, getDocumentReference()),
context);
}
/**
* @since 2.2M2
*/
public BaseObject getXObject(DocumentReference classReference, boolean create, XWikiContext context)
{
try {
BaseObject obj = getXObject(classReference);
if ((obj == null) && create) {
return newXObject(classReference, context);
}
if (obj == null) {
return null;
} else {
return obj;
}
} catch (Exception e) {
return null;
}
}
/**
* @since 3.4M1
*/
public BaseObject getXObject(EntityReference classReference, boolean create, XWikiContext context)
{
try {
BaseObject obj = getXObject(classReference);
if ((obj == null) && create) {
return newXObject(classReference, context);
}
if (obj == null) {
return null;
} else {
return obj;
}
} catch (Exception e) {
return null;
}
}
/**
* @deprecated since 2.2M2 use {@link #getXObject(DocumentReference, boolean, XWikiContext)}
*/
@Deprecated
public BaseObject getObject(String className, boolean create, XWikiContext context)
{
return getXObject(
getXClassEntityReferenceResolver().resolve(className, EntityType.DOCUMENT, getDocumentReference()), create,
context);
}
public boolean validate(XWikiContext context) throws XWikiException
{
return validate(null, context);
}
public boolean validate(String[] classNames, XWikiContext context) throws XWikiException
{
boolean isValid = true;
if ((classNames == null) || (classNames.length == 0)) {
for (DocumentReference classReference : getXObjects().keySet()) {
BaseClass bclass = context.getWiki().getXClass(classReference, context);
List<BaseObject> objects = getXObjects(classReference);
for (BaseObject obj : objects) {
if (obj != null) {
isValid &= bclass.validateObject(obj, context);
}
}
}
} else {
for (String className : classNames) {
List<BaseObject> objects = getXObjects(getCurrentMixedDocumentReferenceResolver().resolve(className));
if (objects != null) {
for (BaseObject obj : objects) {
if (obj != null) {
BaseClass bclass = obj.getXClass(context);
isValid &= bclass.validateObject(obj, context);
}
}
}
}
}
String validationScript = "";
XWikiRequest req = context.getRequest();
if (req != null) {
validationScript = req.get("xvalidation");
}
if ((validationScript == null) || (validationScript.trim().equals(""))) {
validationScript = getValidationScript();
}
if ((validationScript != null) && (!validationScript.trim().equals(""))) {
isValid &= executeValidationScript(context, validationScript);
}
return isValid;
}
public static void backupContext(Map<String, Object> backup, XWikiContext context)
{
// The XWiki Context isn't recreated when the Execution Context is cloned so we have to backup some of its data.
// Backup the current document on the XWiki Context.
backup.put("doc", context.getDoc());
backup.put("cdoc", context.get("cdoc"));
backup.put("tdoc", context.get("tdoc"));
// Backup the secure document
backup.put(CKEY_SDOC, context.get(CKEY_SDOC));
// Clone the Execution Context to provide isolation. The clone will have a new Velocity and Script Context.
Execution execution = Utils.getComponent(Execution.class);
try {
execution.pushContext(Utils.getComponent(ExecutionContextManager.class).clone(execution.getContext()));
} catch (ExecutionContextException e) {
throw new RuntimeException("Failed to clone the Execution Context", e);
}
// Bridge with old XWiki Context, required for legacy code.
execution.getContext().setProperty(XWikiContext.EXECUTIONCONTEXT_KEY, context);
}
public static void restoreContext(Map<String, Object> backup, XWikiContext context)
{
// Restore the Execution Context. This will also restore the previous Velocity and Script Context.
Execution execution = Utils.getComponent(Execution.class);
execution.popContext();
// Restore the current document on the XWiki Context.
context.setDoc((XWikiDocument) backup.get("doc"));
context.put("cdoc", backup.get("cdoc"));
context.put("tdoc", backup.get("tdoc"));
// Restore the secure document
context.put(CKEY_SDOC, backup.get(CKEY_SDOC));
}
public void setAsContextDoc(XWikiContext context)
{
context.setDoc(this);
context.remove("cdoc");
context.remove("tdoc");
// Get rid of secure document (so that it fallback on context document)
context.remove(CKEY_SDOC);
}
/**
* @return the String representation of the previous version of this document or null if this is the first version.
*/
public String getPreviousVersion()
{
XWikiDocumentArchive archive = loadDocumentArchive();
if (archive != null) {
Version prevVersion = archive.getPrevVersion(getRCSVersion());
if (prevVersion != null) {
return prevVersion.toString();
}
}
return null;
}
@Override
public String toString()
{
return getFullName();
}
/**
* Indicates whether the document should be 'hidden' or not, meaning that it should not be returned in public search
* results.
*
* @param hidden The new value of the {@link #hidden} property.
*/
public void setHidden(Boolean hidden)
{
if (hidden == null) {
this.hidden = false;
} else {
this.hidden = hidden;
}
}
/**
* Indicates whether the document is 'hidden' or not, meaning that it should not be returned in public search
* results.
*
* @return <code>true</code> if the document is hidden and does not appear among the results of
* {@link com.xpn.xwiki.api.XWiki#searchDocuments(String)}, <code>false</code> otherwise.
*/
public Boolean isHidden()
{
return this.hidden;
}
/**
* Convert the current document content from its current syntax to the new syntax passed as parameter.
*
* @param targetSyntaxId the syntax to convert to (e.g. {@code xwiki/2.0}, {@code xhtml/1.0}, etc)
* @throws XWikiException if an exception occurred during the conversion process
*/
public void convertSyntax(String targetSyntaxId, XWikiContext context) throws XWikiException
{
try {
convertSyntax(getSyntaxFactory().createSyntaxFromIdString(targetSyntaxId), context);
} catch (Exception e) {
throw new XWikiException(XWikiException.MODULE_XWIKI_RENDERING, XWikiException.ERROR_XWIKI_UNKNOWN,
"Failed to convert document to syntax [" + targetSyntaxId + "]", e);
}
}
/**
* Convert the current document content from its current syntax to the new syntax passed as parameter.
*
* @param targetSyntax the syntax to convert to (e.g. {@code xwiki/2.0}, {@code xhtml/1.0}, etc)
* @throws XWikiException if an exception occurred during the conversion process
*/
public void convertSyntax(Syntax targetSyntax, XWikiContext context) throws XWikiException
{
// convert content
setContent(performSyntaxConversion(getContent(), getDocumentReference(), getSyntax(), targetSyntax));
// convert objects
Map<DocumentReference, List<BaseObject>> objectsByClass = getXObjects();
for (List<BaseObject> objects : objectsByClass.values()) {
for (BaseObject bobject : objects) {
if (bobject != null) {
BaseClass bclass = bobject.getXClass(context);
for (Object fieldClass : bclass.getProperties()) {
if (fieldClass instanceof TextAreaClass && ((TextAreaClass) fieldClass).isWikiContent()) {
TextAreaClass textAreaClass = (TextAreaClass) fieldClass;
LargeStringProperty field = (LargeStringProperty) bobject.getField(textAreaClass.getName());
if (field != null) {
field.setValue(performSyntaxConversion(field.getValue(), getDocumentReference(),
getSyntax(), targetSyntax));
}
}
}
}
}
}
// change syntax
setSyntax(targetSyntax);
}
/**
* NOTE: This method caches the XDOM and returns a clone that can be safely modified.
*
* @return the XDOM corresponding to the document's string content
*/
@Override
public XDOM getXDOM()
{
if (this.xdomCache == null) {
try {
this.xdomCache = parseContent(getContent());
} catch (XWikiException e) {
ErrorBlockGenerator errorBlockGenerator = Utils.getComponent(ErrorBlockGenerator.class);
return new XDOM(errorBlockGenerator.generateErrorBlocks("Failed to render content", e, false));
}
}
return this.xdomCache.clone();
}
/**
* @return true if the document has a xwiki/1.0 syntax content
*/
public boolean is10Syntax()
{
return is10Syntax(getSyntaxId());
}
/**
* @return true if the document has a xwiki/1.0 syntax content
*/
public boolean is10Syntax(String syntaxId)
{
return Syntax.XWIKI_1_0.toIdString().equalsIgnoreCase(syntaxId);
}
private void init(DocumentReference reference)
{
// if the passed reference is null consider it points to the default reference
if (reference == null) {
setDocumentReference(
Utils.<Provider<DocumentReference>>getComponent(DocumentReference.TYPE_PROVIDER).get());
} else {
setDocumentReference(reference);
}
this.updateDate = new Date();
this.updateDate.setTime((this.updateDate.getTime() / 1000) * 1000);
this.contentUpdateDate = new Date();
this.contentUpdateDate.setTime((this.contentUpdateDate.getTime() / 1000) * 1000);
this.creationDate = new Date();
this.creationDate.setTime((this.creationDate.getTime() / 1000) * 1000);
this.content = "";
this.format = "";
this.locale = Locale.ROOT;
this.defaultLocale = Locale.ROOT;
this.customClass = "";
this.comment = "";
// Note: As there's no notion of an Empty document we don't set the original document
// field. Thus getOriginalDocument() may return null.
}
private boolean executeValidationScript(XWikiContext context, String validationScript)
{
try {
ContextualAuthorizationManager authorization = Utils.getComponent(ContextualAuthorizationManager.class);
DocumentReference validationScriptReference =
getCurrentDocumentReferenceResolver().resolve(validationScript, getDocumentReference());
// Make sure target document is allowed to execute Groovy
// TODO: this check should probably be right in XWiki#parseGroovyFromPage
authorization.checkAccess(Right.PROGRAM, validationScriptReference);
XWikiValidationInterface validObject =
(XWikiValidationInterface) context.getWiki().parseGroovyFromPage(validationScript, context);
return validObject.validateDocument(this, context);
} catch (Throwable e) {
XWikiValidationStatus.addExceptionToContext(getFullName(), "", e, context);
return false;
}
}
/**
* Convert the passed content from the passed syntax to the passed new syntax.
*
* @param content the content to convert
* @param source the reference to where the content comes from (eg document reference)
* @param currentSyntaxId the syntax of the current content to convert
* @param targetSyntax the new syntax after the conversion
* @return the converted content in the new syntax
* @throws XWikiException if an exception occurred during the conversion process
* @since 2.4M2
*/
private static String performSyntaxConversion(String content, DocumentReference source, Syntax currentSyntaxId,
Syntax targetSyntax) throws XWikiException
{
try {
XDOM dom = parseContent(currentSyntaxId, content, source);
return performSyntaxConversion(dom, targetSyntax, null);
} catch (Exception e) {
throw new XWikiException(XWikiException.MODULE_XWIKI_RENDERING, XWikiException.ERROR_XWIKI_UNKNOWN,
"Failed to convert document to syntax [" + targetSyntax + "]", e);
}
}
/**
* Convert the passed content from the passed syntax to the passed new syntax.
*
* @param content the XDOM content to convert, the XDOM can be modified during the transformation
* @param targetSyntax the new syntax after the conversion
* @param txContext the context when Transformation are executed or null if transformation shouldn't be executed
* @return the converted content in the new syntax
* @throws XWikiException if an exception occurred during the conversion process
* @since 2.4M2
*/
private static String performSyntaxConversion(XDOM content, Syntax targetSyntax, TransformationContext txContext)
throws XWikiException
{
try {
if (txContext != null) {
// Transform XDOM
TransformationManager transformations = Utils.getComponent(TransformationManager.class);
if (txContext.getXDOM() == null) {
txContext.setXDOM(content);
}
try {
transformations.performTransformations(content, txContext);
} catch (TransformationException te) {
// An error happened during one of the transformations. Since the error has been logged
// continue
// TODO: We should have a visual clue for the user in the future to let him know something
// didn't work as expected.
}
}
// Render XDOM
return renderXDOM(content, targetSyntax);
} catch (Exception e) {
throw new XWikiException(XWikiException.MODULE_XWIKI_RENDERING, XWikiException.ERROR_XWIKI_UNKNOWN,
"Failed to convert document to syntax [" + targetSyntax + "]", e);
}
}
/**
* Render privided XDOM into content of the provided syntax identifier.
*
* @param content the XDOM content to render
* @param targetSyntax the syntax identifier of the rendered content
* @return the rendered content
* @throws XWikiException if an exception occurred during the rendering process
*/
protected static String renderXDOM(XDOM content, Syntax targetSyntax) throws XWikiException
{
try {
BlockRenderer renderer = Utils.getComponent(BlockRenderer.class, targetSyntax.toIdString());
WikiPrinter printer = new DefaultWikiPrinter();
renderer.render(content, printer);
return printer.toString();
} catch (Exception e) {
throw new XWikiException(XWikiException.MODULE_XWIKI_RENDERING, XWikiException.ERROR_XWIKI_UNKNOWN,
"Failed to render document to syntax [" + targetSyntax + "]", e);
}
}
private XDOM parseContent(String content) throws XWikiException
{
return parseContent(getSyntax(), content, getDocumentReference());
}
/**
* @param source the reference to where the content comes from (eg document reference)
*/
private static XDOM parseContent(Syntax syntax, String content, DocumentReference source) throws XWikiException
{
ContentParser parser = Utils.getComponent(ContentParser.class);
try {
return parser.parse(content, syntax, source);
} catch (MissingParserException e) {
throw new XWikiException(XWikiException.MODULE_XWIKI_RENDERING, XWikiException.ERROR_XWIKI_UNKNOWN,
"Failed to find a parser for syntax [" + syntax.toIdString() + "]", e);
} catch (ParseException e) {
throw new XWikiException(XWikiException.MODULE_XWIKI_RENDERING, XWikiException.ERROR_XWIKI_UNKNOWN,
"Failed to parse content of syntax [" + syntax.toIdString() + "]", e);
}
}
/**
* If there's no parser available for the specified syntax default to the XWiki 2.1 syntax.
*/
private Syntax getDefaultDocumentSyntax()
{
Syntax syntax = Utils.getComponent(CoreConfiguration.class).getDefaultDocumentSyntax();
if (syntax == null || (!Utils.getComponentManager().hasComponent(Parser.class, syntax.toIdString())
&& !Syntax.XWIKI_2_1.equals(syntax)))
{
LOGGER.warn("Failed to find parser for the default syntax [{}]. Defaulting to xwiki/2.1 syntax.", syntax);
syntax = Syntax.XWIKI_2_1;
}
return syntax;
}
/**
* Backward-compatibility method to use in order to resolve a class reference passed as a String into a
* DocumentReference proper.
*
* @return the resolved class reference but using this document's wiki if the passed String doesn't specify a wiki,
* the "XWiki" space if the passed String doesn't specify a space and this document's page if the passed
* String doesn't specify a page.
*/
public DocumentReference resolveClassReference(String documentName)
{
DocumentReference defaultReference = new DocumentReference(getDocumentReference().getWikiReference().getName(),
XWiki.SYSTEM_SPACE, getDocumentReference().getName());
return getExplicitDocumentReferenceResolver().resolve(documentName, defaultReference);
}
/**
* Transforms a XClass reference relative to this document into an absolute reference.
*/
private DocumentReference resolveClassReference(EntityReference reference)
{
if (reference instanceof DocumentReference) {
return (DocumentReference) reference;
} else if (reference instanceof LocalDocumentReference) {
return new DocumentReference((LocalDocumentReference) reference, getDocumentReference().getWikiReference());
} else {
DocumentReference defaultReference =
new DocumentReference(getDocumentReference().getWikiReference().getName(), XWiki.SYSTEM_SPACE,
getDocumentReference().getName());
return getExplicitReferenceDocumentReferenceResolver().resolve(reference, defaultReference);
}
}
/**
* Return the reference of the parent document as it stored and passed to
* {@link #setParentReference(EntityReference)}.
* <p>
* You should use {@link #getParentReference()} reference if you want the complete parent reference.
*
* @return the relative parent reference
* @since 2.2.3
*/
public EntityReference getRelativeParentReference()
{
return this.parentReference;
}
private BaseObject prepareXObject(EntityReference classReference)
{
DocumentReference absoluteClassReference = resolveClassReference(classReference);
BaseObject bobject = getXObject(absoluteClassReference);
if (bobject == null) {
bobject = new BaseObject();
bobject.setXClassReference(classReference);
addXObject(bobject);
}
bobject.setDocumentReference(getDocumentReference());
setMetaDataDirty(true);
return bobject;
}
/**
* Apply a 3 ways merge on the current document based on provided previous and new version of the document.
* <p>
* All 3 documents are supposed to have the same document reference and language already since that's what makes
* them uniques.
*
* @param previousDocument the previous version of the document
* @param newDocument the next version of the document
* @param configuration the configuration of the merge indicates how to deal with some conflicts use cases, etc.
* @param context the XWiki context
* @return a repport of what happen during the merge (errors, etc.)
* @since 3.2M1
*/
public MergeResult merge(XWikiDocument previousDocument, XWikiDocument newDocument,
MergeConfiguration configuration, XWikiContext context)
{
MergeResult mergeResult = new MergeResult();
// Title
setTitle(MergeUtils.mergeOject(previousDocument.getTitle(), newDocument.getTitle(), getTitle(), mergeResult));
// Content
setContent(
MergeUtils.mergeLines(previousDocument.getContent(), newDocument.getContent(), getContent(), mergeResult));
// Syntax
setSyntax(
MergeUtils.mergeOject(previousDocument.getSyntax(), newDocument.getSyntax(), getSyntax(), mergeResult));
// Default locale
setDefaultLocale(MergeUtils.mergeOject(previousDocument.getDefaultLocale(), newDocument.getDefaultLocale(),
getDefaultLocale(), mergeResult));
// Parent
setParentReference(MergeUtils.mergeOject(previousDocument.getRelativeParentReference(),
newDocument.getRelativeParentReference(), getRelativeParentReference(), mergeResult));
// DefaultTemplate
setDefaultTemplate(MergeUtils.mergeOject(previousDocument.getDefaultTemplate(),
newDocument.getDefaultTemplate(), getDefaultTemplate(), mergeResult));
// Hidden
setHidden(MergeUtils.mergeOject(previousDocument.isHidden(), newDocument.isHidden(), isHidden(), mergeResult));
// CustomClass
setCustomClass(MergeUtils.mergeLines(previousDocument.getCustomClass(), newDocument.getCustomClass(),
getCustomClass(), mergeResult));
// ValidationScript
setValidationScript(MergeUtils.mergeLines(previousDocument.getValidationScript(),
newDocument.getValidationScript(), getValidationScript(), mergeResult));
// Objects
List<List<ObjectDiff>> objectsDiff = previousDocument.getObjectDiff(previousDocument, newDocument, context);
if (!objectsDiff.isEmpty()) {
// Apply diff on result
for (List<ObjectDiff> objectClassDiff : objectsDiff) {
for (ObjectDiff diff : objectClassDiff) {
BaseObject objectResult = getXObject(diff.getXClassReference(), diff.getNumber());
BaseObject previousObject =
previousDocument.getXObject(diff.getXClassReference(), diff.getNumber());
BaseObject newObject = newDocument.getXObject(diff.getXClassReference(), diff.getNumber());
PropertyInterface propertyResult =
objectResult != null ? objectResult.getField(diff.getPropName()) : null;
PropertyInterface previousProperty =
previousObject != null ? previousObject.getField(diff.getPropName()) : null;
PropertyInterface newProperty = newObject != null ? newObject.getField(diff.getPropName()) : null;
if (diff.getAction() == ObjectDiff.ACTION_OBJECTADDED) {
if (objectResult == null) {
setXObject(newObject.getNumber(),
configuration.isProvidedVersionsModifiables() ? newObject : newObject.clone());
mergeResult.setModified(true);
} else {
// collision between DB and new: object to add but already exists in the DB
mergeResult.getLog().error("Collision found on object [{}]", objectResult.getReference());
}
} else if (diff.getAction() == ObjectDiff.ACTION_OBJECTREMOVED) {
if (objectResult != null) {
if (objectResult.equals(previousObject)) {
removeXObject(objectResult);
mergeResult.setModified(true);
} else {
// collision between DB and new: object to remove but not the same as previous
// version
mergeResult.getLog().error("Collision found on object [{}]",
objectResult.getReference());
}
} else {
// Already removed from DB, lets assume the user is prescient
mergeResult.getLog().warn("Object [{}] already removed", previousObject.getReference());
}
} else if (previousObject != null && newObject != null) {
if (objectResult != null) {
if (diff.getAction() == ObjectDiff.ACTION_PROPERTYADDED) {
if (propertyResult == null) {
objectResult.safeput(diff.getPropName(), newProperty);
mergeResult.setModified(true);
} else {
// collision between DB and new: property to add but already exists in the DB
mergeResult.getLog().error("Collision found on object property [{}]",
propertyResult.getReference());
}
} else if (diff.getAction() == ObjectDiff.ACTION_PROPERTYREMOVED) {
if (propertyResult != null) {
if (propertyResult.equals(previousProperty)) {
objectResult.removeField(diff.getPropName());
mergeResult.setModified(true);
} else {
// collision between DB and new: supposed to be removed but the DB version is
// not the same as the previous version
mergeResult.getLog().error("Collision found on object property [{}]",
propertyResult.getReference());
}
} else {
// Already removed from DB, lets assume the user is prescient
mergeResult.getLog().warn("Object property [{}] already removed",
previousProperty.getReference());
}
} else if (diff.getAction() == ObjectDiff.ACTION_PROPERTYCHANGED) {
if (propertyResult != null) {
if (propertyResult.equals(previousProperty)) {
objectResult.safeput(diff.getPropName(), newProperty);
mergeResult.setModified(true);
} else {
// Try to apply a 3 ways merge on the property
propertyResult.merge(previousProperty, newProperty, configuration, context,
mergeResult);
}
} else {
// collision between DB and new: property to modify but does not exists in DB
// Lets assume it's a mistake to fix
mergeResult.getLog().warn("Object [{}] does not exists",
newProperty.getReference());
objectResult.safeput(diff.getPropName(), newProperty);
mergeResult.setModified(true);
}
}
} else {
// Object explitely removed from the DB, lets assume we don't care about the changes
mergeResult.getLog().warn("Object [{}] already removed", previousObject.getReference());
}
}
}
}
}
// Class
BaseClass classResult = getXClass();
BaseClass previousClass = previousDocument.getXClass();
BaseClass newClass = newDocument.getXClass();
classResult.merge(previousClass, newClass, configuration, context, mergeResult);
// Attachments
List<AttachmentDiff> attachmentsDiff =
previousDocument.getAttachmentDiff(previousDocument, newDocument, context);
if (!attachmentsDiff.isEmpty()) {
// Apply deleted attachment diff on result (new attachment has already been saved)
for (AttachmentDiff diff : attachmentsDiff) {
XWikiAttachment previousAttachment = diff.getOrigAttachment();
XWikiAttachment nextAttachment = diff.getNewAttachment();
XWikiAttachment attachment = getAttachment(diff.getFileName());
switch (diff.getType()) {
case DELETE:
if (attachment != null) {
try {
if (attachment.equalsData(previousAttachment, context)) {
removeAttachment(attachment);
mergeResult.setModified(true);
} else {
// collision between DB and new: attachment modified by user
mergeResult.getLog().error("Collision found on attachment [{}]",
attachment.getReference());
}
} catch (XWikiException e) {
mergeResult.getLog().error("Failed to compare attachments with reference [{}]",
attachment.getReference());
}
} else {
// Already removed from DB, lets assume the user is prescient
mergeResult.getLog().warn("Attachment [{}] already removed",
previousAttachment.getReference());
}
break;
case INSERT:
if (attachment != null) {
try {
if (!attachment.equalsData(nextAttachment, context)) {
// collision between DB and new: attachment to add but a different one already
// exists in the DB
mergeResult.getLog().error("Collision found on attachment [{}]",
attachment.getReference());
} else {
// Already added to the DB, lets assume the user is prescient
mergeResult.getLog().warn("Attachment [{}] already added",
previousAttachment.getReference());
}
} catch (XWikiException e) {
mergeResult.getLog().error("Failed to compare attachments with reference [{}]",
attachment.getReference());
}
} else {
addAttachment(configuration.isProvidedVersionsModifiables() ? nextAttachment
: (XWikiAttachment) nextAttachment.clone());
mergeResult.setModified(true);
}
break;
case CHANGE:
if (attachment != null) {
attachment.merge(previousAttachment, nextAttachment, configuration, context, mergeResult);
} else {
// collision between DB and new: attachment modified but does not exist in the DB
mergeResult.getLog().error("Collision found on attachment [{}]",
previousAttachment.getReference());
}
break;
default:
break;
}
}
}
return mergeResult;
}
/**
* Apply modification comming from provided document.
* <p>
* Thid method does not take into account versions and author related informations and the provided document should
* have the same reference. Like {@link #merge(XWikiDocument, XWikiDocument, MergeConfiguration, XWikiContext)},
* this method is dealing with "real" data and should not change anything related to version management and document
* identifier.
* <p>
* Important note: this method does not take care of attachments contents related operations and only remove
* attachments which need to be removed from the list. For memory handling reasons all attachments contents related
* operations should be done elsewhere.
*
* @param document the document to apply
* @return false is nothing changed
*/
public boolean apply(XWikiDocument document)
{
return apply(document, true);
}
/**
* Apply modification comming from provided document.
* <p>
* Thid method does not take into account versions and author related informations and the provided document should
* have the same reference. Like {@link #merge(XWikiDocument, XWikiDocument, MergeConfiguration, XWikiContext)},
* this method is dealing with "real" data and should not change everything related to version management and
* document identifier.
*
* @param document the document to apply
* @return false is nothing changed
*/
public boolean apply(XWikiDocument document, boolean clean)
{
boolean modified = false;
// /////////////////////////////////
// Document
if (!StringUtils.equals(getContent(), document.getContent())) {
setContent(document.getContent());
modified = true;
}
if (ObjectUtils.notEqual(getSyntax(), document.getSyntax())) {
setSyntax(document.getSyntax());
modified = true;
}
if (ObjectUtils.notEqual(getDefaultLocale(), document.getDefaultLocale())) {
setDefaultLocale(document.getDefaultLocale());
modified = true;
}
if (!StringUtils.equals(getTitle(), document.getTitle())) {
setTitle(document.getTitle());
modified = true;
}
if (!StringUtils.equals(getDefaultTemplate(), document.getDefaultTemplate())) {
setDefaultTemplate(document.getDefaultTemplate());
modified = true;
}
if (ObjectUtils.notEqual(getRelativeParentReference(), document.getRelativeParentReference())) {
setParentReference(document.getRelativeParentReference());
modified = true;
}
if (!StringUtils.equals(getCustomClass(), document.getCustomClass())) {
setCustomClass(document.getCustomClass());
modified = true;
}
if (!StringUtils.equals(getValidationScript(), document.getValidationScript())) {
setValidationScript(document.getValidationScript());
modified = true;
}
if (isHidden() != document.isHidden()) {
setHidden(document.isHidden());
modified = true;
}
if (!StringUtils.equals(getValidationScript(), document.getValidationScript())) {
setValidationScript(document.getValidationScript());
modified = true;
}
// /////////////////////////////////
// XObjects
if (clean) {
// Delete objects that don't exist anymore
for (List<BaseObject> objects : getXObjects().values()) {
// Duplicate the list since we are potentially going to modify it
for (BaseObject originalObj : new ArrayList<BaseObject>(objects)) {
if (originalObj != null) {
BaseObject newObj =
document.getXObject(originalObj.getXClassReference(), originalObj.getNumber());
if (newObj == null) {
// The object was deleted
removeXObject(originalObj);
modified = true;
}
}
}
}
}
// Add new objects or update existing objects
for (List<BaseObject> objects : document.getXObjects().values()) {
for (BaseObject newObj : objects) {
if (newObj != null) {
BaseObject originalObj = getXObject(newObj.getXClassReference(), newObj.getNumber());
if (originalObj == null) {
// The object added or modified
setXObject(newObj.getNumber(), newObj);
modified = true;
} else {
// The object added or modified
modified |= originalObj.apply(newObj, clean);
}
}
}
}
// /////////////////////////////////
// XClass
modified |= getXClass().apply(document.getXClass(), clean);
if (ObjectUtils.notEqual(getXClassXML(), document.getXClassXML())) {
setXClassXML(document.getXClassXML());
modified = true;
}
// /////////////////////////////////
// Attachments
if (clean) {
// Delete attachments that don't exist anymore
for (XWikiAttachment attachment : new ArrayList<XWikiAttachment>(getAttachmentList())) {
if (document.getAttachment(attachment.getFilename()) == null) {
removeAttachment(attachment);
}
}
}
// Add new attachments or update existing attachments
for (XWikiAttachment attachment : document.getAttachmentList()) {
XWikiAttachment originalAttachment = getAttachment(attachment.getFilename());
if (originalAttachment == null) {
addAttachment(attachment);
} else {
originalAttachment.apply(attachment);
}
}
return modified;
}
}