Java tutorial
/* * 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.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.StringReader; import java.lang.ref.SoftReference; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.net.URL; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; 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.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Stack; 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.servlet.http.HttpServletRequest; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.BooleanUtils; import org.apache.commons.lang.ObjectUtils; import org.apache.commons.lang.StringUtils; import org.apache.velocity.VelocityContext; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.dom.DOMDocument; import org.dom4j.dom.DOMElement; import org.dom4j.io.OutputFormat; import org.dom4j.io.SAXReader; 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.context.Execution; import org.xwiki.context.ExecutionContext; import org.xwiki.context.ExecutionContextException; import org.xwiki.context.ExecutionContextManager; import org.xwiki.model.EntityType; import org.xwiki.model.reference.DocumentReference; import org.xwiki.model.reference.DocumentReferenceResolver; import org.xwiki.model.reference.EntityReference; import org.xwiki.model.reference.EntityReferenceResolver; import org.xwiki.model.reference.EntityReferenceSerializer; import org.xwiki.model.reference.ObjectReference; import org.xwiki.model.reference.WikiReference; 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.MacroBlockMatcher; import org.xwiki.rendering.listener.HeaderLevel; import org.xwiki.rendering.listener.MetaData; import org.xwiki.rendering.listener.reference.DocumentResourceReference; import org.xwiki.rendering.listener.reference.ResourceReference; import org.xwiki.rendering.listener.reference.ResourceType; 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.TransformationContext; import org.xwiki.rendering.transformation.TransformationException; import org.xwiki.rendering.transformation.TransformationManager; import org.xwiki.rendering.util.ParserUtils; import org.xwiki.velocity.VelocityManager; import org.xwiki.velocity.XWikiVelocityException; 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.content.Link; import com.xpn.xwiki.content.parsers.DocumentParser; import com.xpn.xwiki.content.parsers.RenamePageReplaceLinkHandler; import com.xpn.xwiki.content.parsers.ReplacementResultCollection; import com.xpn.xwiki.criteria.impl.RevisionCriteria; import com.xpn.xwiki.doc.merge.CollisionException; import com.xpn.xwiki.doc.merge.MergeConfiguration; import com.xpn.xwiki.doc.merge.MergeResult; import com.xpn.xwiki.doc.merge.MergeUtils; import com.xpn.xwiki.doc.rcs.XWikiRCSNodeInfo; import com.xpn.xwiki.internal.cache.rendering.RenderingCache; 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.plugin.query.XWikiCriteria; import com.xpn.xwiki.render.XWikiVelocityRenderer; 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.Utils; import com.xpn.xwiki.web.XWikiMessageTool; import com.xpn.xwiki.web.XWikiRequest; public class XWikiDocument implements DocumentModelBridge { private static final Logger LOGGER = LoggerFactory.getLogger(XWikiDocument.class); /** * 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) ?([^>]*+)>"); /** Regex for finding the first level 1 or 2 heading in the document title, to be used as the document title. */ private static final Pattern HEADING_PATTERN_10 = Pattern.compile("^\\s*+1(?:\\.1)?\\s++(.++)$", Pattern.MULTILINE); 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; /** * 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; 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; protected String language; private String defaultLanguage; private int translation; /** * 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 Confluence 2.0 syntax while another one uses the XWiki 1.0 syntax. In practice our * first need is to support the new rendering component. To use the old rendering implementation specify a * "xwiki/1.0" syntaxId and use a "xwiki/2.0" syntaxId for using the new rendering component. */ 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; public static final int HAS_ATTACHMENTS = 1; public static final int HAS_OBJECTS = 2; public static final int HAS_CLASS = 4; private int elements = HAS_OBJECTS | HAS_ATTACHMENTS; /** * 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 = "."; // 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 List<XWikiAttachment> attachmentList; // Caching private boolean fromCache = false; private List<BaseObject> xObjectsToRemove = new ArrayList<BaseObject>(); /** * 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; /** * 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 xdom; /** * Used to resolve a string into a proper Document Reference using the current document's reference to fill the * blanks. */ @SuppressWarnings("unchecked") private DocumentReferenceResolver<String> currentDocumentReferenceResolver = Utils .getComponent(DocumentReferenceResolver.class, "current"); /** * Used to resolve a string into a proper Document Reference. */ @SuppressWarnings("unchecked") private DocumentReferenceResolver<String> explicitDocumentReferenceResolver = Utils .getComponent(DocumentReferenceResolver.class, "explicit"); @SuppressWarnings("unchecked") private EntityReferenceResolver<String> xClassEntityReferenceResolver = Utils .getComponent(EntityReferenceResolver.class, "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. */ @SuppressWarnings("unchecked") private DocumentReferenceResolver<String> currentMixedDocumentReferenceResolver = Utils .getComponent(DocumentReferenceResolver.class, "currentmixed"); /** * Used to normalize references. */ @SuppressWarnings("unchecked") private DocumentReferenceResolver<EntityReference> currentReferenceDocumentReferenceResolver = Utils .getComponent(DocumentReferenceResolver.class, "current/reference"); /** * Used to normalize references. */ @SuppressWarnings("unchecked") private DocumentReferenceResolver<EntityReference> explicitReferenceDocumentReferenceResolver = Utils .getComponent(DocumentReferenceResolver.class, "explicit/reference"); /** * Used to resolve parent references in the way they are stored externally (database, xml, etc), ie relative or * absolute. */ @SuppressWarnings("unchecked") private EntityReferenceResolver<String> relativeEntityReferenceResolver = Utils .getComponent(EntityReferenceResolver.class, "relative"); /** * Used to convert a proper Document Reference to string (compact form). */ @SuppressWarnings("unchecked") private EntityReferenceSerializer<String> compactEntityReferenceSerializer = Utils .getComponent(EntityReferenceSerializer.class, "compact"); /** * Used to convert a proper Document Reference to string (standard form). */ @SuppressWarnings("unchecked") private EntityReferenceSerializer<String> defaultEntityReferenceSerializer = Utils .getComponent(EntityReferenceSerializer.class); /** * Used to convert a Document Reference to string (compact form without the wiki part if it matches the current * wiki). */ @SuppressWarnings("unchecked") private EntityReferenceSerializer<String> compactWikiEntityReferenceSerializer = Utils .getComponent(EntityReferenceSerializer.class, "compactwiki"); /** * Used to convert a proper Document Reference to a string but without the wiki name. */ @SuppressWarnings("unchecked") private EntityReferenceSerializer<String> localEntityReferenceSerializer = Utils .getComponent(EntityReferenceSerializer.class, "local"); /** * Used to emulate an inline parsing. */ private ParserUtils parserUtils = new ParserUtils(); /** * Used to create proper {@link Syntax} objects. */ private SyntaxFactory syntaxFactory = Utils.getComponent(SyntaxFactory.class); private RenderingCache renderingCache = Utils.getComponent(RenderingCache.class); /** * @since 2.2M1 */ public XWikiDocument(DocumentReference reference) { init(reference); } /** * @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); if (!StringUtils.isEmpty(wiki)) { contextReference.setParent(new WikiReference(wiki)); } } else if (!StringUtils.isEmpty(wiki)) { contextReference = new WikiReference(wiki); } DocumentReference reference; if (contextReference != null) { reference = this.currentDocumentReferenceResolver.resolve(name, contextReference); // Replace the resolved wiki by the passed wiki if not empty/null if (!StringUtils.isEmpty(wiki)) { reference.setWikiReference(new WikiReference(wiki)); } } else { // Both the wiki and space params are empty/null, thus don't use a context reference. reference = this.currentDocumentReferenceResolver.resolve(name); } init(reference); } 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; } /** * @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: The implemented below doesn't guarantee a unique id since it uses the hashCode() method which doesn't // guarantee unicity. From the JDK's javadoc: "It is not required that if two objects are unequal according to // the equals(java.lang.Object) method, then calling the hashCode method on each of the two objects must // produce distinct integer results.". 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. if ((this.language == null) || this.language.trim().equals("")) { this.id = this.localEntityReferenceSerializer.serialize(getDocumentReference()).hashCode(); } else { this.id = (this.localEntityReferenceSerializer.serialize(getDocumentReference()) + ":" + this.language) .hashCode(); } return this.id; } /** * @see #getId() */ public void setId(long id) { this.id = id; } /** * 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 * @deprecated since 2.2M1 used {@link #getDocumentReference()} instead */ @Deprecated public String getSpace() { return getDocumentReference().getLastSpaceReference().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 setSpace(String space) { if (space != null) { getDocumentReference().getLastSpaceReference().setName(space); // Clean the absolute parent reference cache to rebuild it next time getParentReference is called. this.parentReferenceCache = null; } } /** * 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 * @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); } 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). */ 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 = this.explicitReferenceDocumentReferenceResolver .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 = this.defaultEntityReferenceSerializer.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 ((parentReference == null && getRelativeParentReference() != null) || (parentReference != null && !parentReference.equals(getRelativeParentReference()))) { this.parentReference = parentReference; // Clean the absolute parent reference cache to rebuild it next time getParentReference is called. this.parentReferenceCache = null; setMetaDataDirty(true); } } /** * Convert a full document reference into the proper relative document reference (wiki part is removed if it's the * same as document wiki) to store as parent. * * @deprecated since 2.2.3 use {@link #setParentReference(org.xwiki.model.reference.EntityReference)} instead */ @Deprecated public void setParentReference(DocumentReference parentReference) { if (parentReference != null) { setParent(serializeReference(parentReference, this.compactWikiEntityReferenceSerializer, getDocumentReference())); } else { setParentReference((EntityReference) null); } } /** * 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 used {@link #setParentReference(DocumentReference)} 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(this.relativeEntityReferenceResolver.resolve(parent, EntityType.DOCUMENT)); } } public String getContent() { return this.content; } public void setContent(String content) { if (content == null) { content = ""; } if (!content.equals(this.content)) { setContentDirty(true); setWikiNode(null); } this.content = content; // invalidate parsed xdom this.xdom = null; } public void setContent(XDOM content) throws XWikiException { setContent(renderXDOM(content, getSyntax())); } public String getRenderedContent(Syntax targetSyntax, XWikiContext context) throws XWikiException { return getRenderedContent(targetSyntax, true, context); } public String getRenderedContent(Syntax targetSyntax, boolean isolateVelocityMacros, XWikiContext context) throws XWikiException { // Note: We are currently duplicating code from the other 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 // http://jira.xwiki.org/jira/browse/XWIKI-4274 for more details. String content = getTranslatedContent(context); String renderedContent = this.renderingCache.getRenderedContent(getDocumentReference(), content, context); String documentName = this.defaultEntityReferenceSerializer.serialize( isolateVelocityMacros ? getDocumentReference() : context.getDoc().getDocumentReference()); if (renderedContent == null) { Object isInRenderingEngine = context.get("isInRenderingEngine"); // Mark that we're starting to use the current document as a macro namespace if (isolateVelocityMacros && (isInRenderingEngine == null || isInRenderingEngine == Boolean.FALSE)) { try { Utils.getComponent(VelocityManager.class).getVelocityEngine() .startedUsingMacroNamespace(documentName); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Started using velocity macro namespace [" + documentName + "]"); } } catch (XWikiVelocityException e) { // Failed to get the Velocity Engine and this to clear Velocity Macro cache. Log this as a warning // but continue since it's not absolutely critical. LOGGER.warn("Failed to notify Velocity Macro cache for the [" + documentName + "] namespace. Reason = [" + e.getMessage() + "]"); } } try { // This tells display() methods that we are inside the rendering engine and thus // that they can return wiki syntax and not HTML syntax (which is needed when // outside the rendering engine, i.e. when we're inside templates using only // Velocity for example). context.put("isInRenderingEngine", true); // If the Syntax id is "xwiki/1.0" then use the old rendering subsystem. Otherwise use the new one. if (is10Syntax()) { renderedContent = context.getWiki().getRenderingEngine().renderDocument(this, context); } else { TransformationContext txContext = new TransformationContext(); txContext.setSyntax(getSyntax()); txContext.setId(documentName); renderedContent = performSyntaxConversion(content, documentName, targetSyntax, txContext); } this.renderingCache.setRenderedContent(getDocumentReference(), content, renderedContent, context); } finally { if (isInRenderingEngine != null) { context.put("isInRenderingEngine", isInRenderingEngine); } else { context.remove("isInRenderingEngine"); } // Since we configure Velocity to have local macros (i.e. macros visible only to the local context), // since Velocity caches the velocity macros in a local cache (we use key which is the absolute // document reference) and since documents can include other documents or panels, we need to make sure // we empty the local Velocity macro cache at the end of the rendering for the document as otherwise the // local Velocity macro caches will keep growing as users create new pages. // // Note that we check if we are in the rendering engine as this cleanup must be done only once after the // document has been rendered but this method can be called recursively. We know it's the initial entry // point when isInRenderingEngine is false... if (isolateVelocityMacros && (isInRenderingEngine == null || isInRenderingEngine == Boolean.FALSE)) { try { Utils.getComponent(VelocityManager.class).getVelocityEngine() .stoppedUsingMacroNamespace(documentName); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Stopped using velocity macro namespace [" + documentName + "]"); } } catch (XWikiVelocityException e) { // Failed to get the Velocity Engine and this to clear Velocity Macro cache. Log this as a // warning // but continue since it's not absolutely critical. LOGGER.warn("Failed to notify Velocity Macro cache for the [" + documentName + "] namespace. Reason = [" + e.getMessage() + "]"); } } } } return renderedContent; } public String getRenderedContent(XWikiContext context) throws XWikiException { return getRenderedContent(Syntax.XHTML_1_0, context); } /** * @param text the text to render * @param syntaxId the id of the Syntax used by the passed text (for example: "xwiki/1.0") * @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, Syntax.XHTML_1_0.toIdString(), context); } /** * @param text the text to render * @param sourceSyntaxId the id of the Syntax used by the passed text (for example: "xwiki/1.0") * @param targetSyntaxId the id of the syntax in which to render the document content * @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) { String result = this.renderingCache.getRenderedContent(getDocumentReference(), text, context); String documentName = this.defaultEntityReferenceSerializer.serialize(getDocumentReference()); if (result == null) { Map<String, Object> backup = new HashMap<String, Object>(); Object isInRenderingEngine = context.get("isInRenderingEngine"); try { backupContext(backup, context); setAsContextDoc(context); // Mark that we're starting to use the current document as a macro namespace if (isInRenderingEngine == null || isInRenderingEngine == Boolean.FALSE) { try { Utils.getComponent(VelocityManager.class).getVelocityEngine() .startedUsingMacroNamespace(documentName); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Started using velocity macro namespace [" + documentName + "]"); } } catch (XWikiVelocityException e) { // Failed to get the Velocity Engine and this to clear Velocity Macro cache. Log this as a // warning // but continue since it's not absolutely critical. LOGGER.warn("Failed to notify Velocity Macro cache for the [" + documentName + "] namespace. Reason = [" + e.getMessage() + "]"); } } // This tells display() methods that we are inside the rendering engine and thus // that they can return wiki syntax and not HTML syntax (which is needed when // outside the rendering engine, i.e. when we're inside templates using only // Velocity for example). context.put("isInRenderingEngine", true); // If the Syntax id is "xwiki/1.0" then use the old rendering subsystem. Otherwise use the new one. if (is10Syntax(sourceSyntaxId)) { result = context.getWiki().getRenderingEngine().renderText(text, this, context); } else { SyntaxFactory syntaxFactory = Utils.getComponent(SyntaxFactory.class); TransformationContext txContext = new TransformationContext(); txContext.setSyntax(syntaxFactory.createSyntaxFromIdString(sourceSyntaxId)); txContext.setId(documentName); result = performSyntaxConversion(text, documentName, syntaxFactory.createSyntaxFromIdString(targetSyntaxId), txContext); } this.renderingCache.setRenderedContent(getDocumentReference(), text, result, context); } 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); result = ""; } finally { restoreContext(backup, context); if (isInRenderingEngine != null) { context.put("isInRenderingEngine", isInRenderingEngine); } else { context.remove("isInRenderingEngine"); } // Since we configure Velocity to have local macros (i.e. macros visible only to the local context), // since Velocity caches the velocity macros in a local cache (we use key which is the absolute // document reference) and since documents can include other documents or panels, we need to make sure // we empty the local Velocity macro cache at the end of the rendering for the document as otherwise the // local Velocity macro caches will keep growing as users create new pages. // // Note that we check if we are in the rendering engine as this cleanup must be done only once after the // document has been rendered but this method can be called recursively. We know it's the initial entry // point when isInRenderingEngine is false... if (isInRenderingEngine == null || isInRenderingEngine == Boolean.FALSE) { try { Utils.getComponent(VelocityManager.class).getVelocityEngine() .stoppedUsingMacroNamespace(documentName); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Stopped using velocity macro namespace [" + documentName + "]"); } } catch (XWikiVelocityException e) { // Failed to get the Velocity Engine and this to clear Velocity Macro cache. Log this as a // warning // but continue since it's not absolutely critical. LOGGER.warn("Failed to notify Velocity Macro cache for the [" + documentName + "] namespace. Reason = [" + e.getMessage() + "]"); } } } } return result; } 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) { getDocumentReference().setName(name); // Clean the absolute parent reference cache to rebuild it next time getParentReference is called. this.parentReferenceCache = null; } } /** * {@inheritDoc} * * @see org.xwiki.bridge.DocumentModelBridge#getDocumentReference() * @since 2.2M1 */ public DocumentReference getDocumentReference() { return this.documentReference; } /** * @return the document's space + page name (eg "space.page") * @deprecated since 2.2M1 use {@link #getDocumentReference()} instead */ @Deprecated public String getFullName() { return this.localEntityReferenceSerializer.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 this.defaultEntityReferenceSerializer.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) { if (!reference.equals(getDocumentReference())) { this.documentReference = reference; setMetaDataDirty(true); // Clean the absolute parent reference cache to rebuild it next time getParentReference is called. 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(this.currentMixedDocumentReferenceResolver.resolve(fullName)); } } /** * {@inheritDoc} * * @see org.xwiki.bridge.DocumentModelBridge#getDocumentName() * @deprecated replaced by {@link #getDocumentReference()} since 2.2M1 */ @Deprecated public org.xwiki.bridge.DocumentName getDocumentName() { return new org.xwiki.bridge.DocumentName(getWikiName(), getSpaceName(), getPageName()); } /** * {@inheritDoc} * * @see DocumentModelBridge#getWikiName() * @deprecated since 2.2M1 use {@link #getDocumentReference()} instead */ @Deprecated public String getWikiName() { return getDatabase(); } /** * {@inheritDoc} * * @see DocumentModelBridge#getSpaceName() * @deprecated since 2.2M1 use {@link #getDocumentReference()} instead */ @Deprecated public String getSpaceName() { return this.getSpace(); } /** * {@inheritDoc} * * @see DocumentModelBridge#getSpaceName() * @deprecated since 2.2M1 use {@link #getDocumentReference()} instead */ @Deprecated public String getPageName() { return this.getName(); } public String getTitle() { return (this.title != null) ? this.title : ""; } /** * @param context the XWiki context used to get access to the com.xpn.xwiki.render.XWikiRenderingEngine object * @return the document title. If a title has not been provided, look for a section title in the document's content * and if not found return the page name. The returned title is also interpreted which means it's allowed to * use Velocity, Groovy, etc syntax within a title. * @deprecated use {@link #getRenderedTitle(Syntax, XWikiContext)} instead */ @Deprecated public String getDisplayTitle(XWikiContext context) { return getRenderedTitle(Syntax.XHTML_1_0, context); } /** * The first found first or second level header content is rendered with * {@link com.xpn.xwiki.render.XWikiRenderingEngine#interpretText(String, XWikiDocument, XWikiContext)}. * * @param context the XWiki context * @return the rendered version of the found header content. Empty string if not can be found. */ private String getRenderedContentTitle10(XWikiContext context) { // 1) Check if the user has provided a title String title = extractTitle10(); // 3) Last if a title has been found renders it as it can contain macros, velocity code, // groovy, etc. if (title.length() > 0) { // Only needed for xwiki 1.0 syntax, for other syntaxes it's already rendered in #extractTitle // This will not completely work for scripting code in title referencing variables // defined elsewhere. In that case it'll only work if those variables have been // parsed and put in the corresponding scripting context. This will not work for // breadcrumbs for example. title = context.getWiki().getRenderingEngine().interpretText(title, this, context); } return title; } /** * Get the rendered version of the first or second level first found header content in the document content. * <ul> * <li>xwiki/1.0: the first found first or second level header content is rendered with * {@link com.xpn.xwiki.render.XWikiRenderingEngine#interpretText(String, XWikiDocument, XWikiContext)}</li> * <li>xwiki/2.0: the first found first or second level content is executed and rendered with renderer for the * provided syntax</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 title. null or empty (when xwiki/1.0 syntax) string if none can be found * @throws XWikiException failed to render content */ private String getRenderedContentTitle(Syntax outputSyntax, XWikiContext context) throws XWikiException { String title = null; // Protect against cycles. For example that cold happen with a call to getRenderedTitle on current document from // a script in the first heading block title @SuppressWarnings("unchecked") Stack<DocumentReference> stackTrace = (Stack<DocumentReference>) context .get("internal.getRenderedContentTitleStackTrace"); if (stackTrace == null) { stackTrace = new Stack<DocumentReference>(); context.put("internal.getRenderedContentTitleStackTrace", stackTrace); } else if (stackTrace.contains(getDocumentReference())) { // TODO: generate an error message instead ? return null; } stackTrace.push(getDocumentReference()); try { // Extract and render the document title if (is10Syntax()) { title = getRenderedContentTitle10(context); } else { List<HeaderBlock> blocks = getXDOM().getChildrenByType(HeaderBlock.class, true); if (!blocks.isEmpty()) { HeaderBlock header = blocks.get(0); // Check the header depth after which we should return null if no header was found. int titleHeaderDepth = (int) context.getWiki().ParamAsLong("xwiki.title.headerdepth", 2); if (header.getLevel().getAsInt() <= titleHeaderDepth) { XDOM headerXDOM = new XDOM(Collections.<Block>singletonList(header)); // Transform try { TransformationContext txContext = new TransformationContext(headerXDOM, getSyntax()); Utils.getComponent(TransformationManager.class).performTransformations(headerXDOM, txContext); } catch (TransformationException e) { // 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 Block headerBlock = headerXDOM.getChildren().get(0); if (headerBlock instanceof HeaderBlock) { title = renderXDOM(new XDOM(headerBlock.getChildren()), outputSyntax); } } } } } finally { stackTrace.pop(); } return title; } /** * Get the rendered version of the title of the document. * <ul> * <li>if document <code>title</code> field is not empty: it's returned after a call to * {@link com.xpn.xwiki.render.XWikiRenderingEngine#interpretText(String, XWikiDocument, XWikiContext)}</li> * <li>if document <code>title</code> field is empty: see {@link #getRenderedContentTitle(Syntax, XWikiContext)}</li> * <li>if after the two first step the title is still empty, 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 title */ public String getRenderedTitle(Syntax outputSyntax, XWikiContext context) { // 1) Check if the user has provided a title String title = getTitle(); try { if (!StringUtils.isEmpty(title)) { title = context.getWiki().getRenderingEngine().interpretText(title, this, context); // If there's been an error during the Velocity evaluation then consider that the title is empty as a // fallback. // TODO: Since interpretText() never throws an exception it's hard to know if there's been an error. // Right now interpretText() returns some HTML when there's an error, so we need to check the returned // result for some marker to decide if an error has occurred... Fix this by refactoring the whole // system used for Velocity evaluation. if (title.indexOf("<div id=\"xwikierror") == -1) { if (!outputSyntax.equals(Syntax.HTML_4_01) && !outputSyntax.equals(Syntax.XHTML_1_0)) { XDOM xdom = parseContent(Syntax.HTML_4_01.toIdString(), title); this.parserUtils.removeTopLevelParagraph(xdom.getChildren()); title = renderXDOM(xdom, outputSyntax); } return title; } } } catch (Exception e) { LOGGER.warn("Failed to interpret title of document [" + this.defaultEntityReferenceSerializer.serialize(getDocumentReference()) + "]", e); } try { // 2) If not, then try to extract the title from the first document section title title = getRenderedContentTitle(outputSyntax, context); } catch (Exception e) { LOGGER.warn("Failed to extract title from content of document [" + this.defaultEntityReferenceSerializer.serialize(getDocumentReference()) + "]", e); } // 3) No title has been found, return the page name as the title if (StringUtils.isEmpty(title)) { title = getDocumentReference().getName(); } return title; } public String extractTitle() { String title = ""; try { if (is10Syntax()) { title = extractTitle10(); } else { List<HeaderBlock> blocks = getXDOM().getChildrenByType(HeaderBlock.class, true); if (!blocks.isEmpty()) { HeaderBlock header = blocks.get(0); if (header.getLevel().compareTo(HeaderLevel.LEVEL2) <= 0) { XDOM headerXDOM = new XDOM(Collections.<Block>singletonList(header)); // transform TransformationContext context = new TransformationContext(headerXDOM, getSyntax()); Utils.getComponent(TransformationManager.class).performTransformations(headerXDOM, context); // render Block headerBlock = headerXDOM.getChildren().get(0); if (headerBlock instanceof HeaderBlock) { title = renderXDOM(new XDOM(headerBlock.getChildren()), Syntax.XHTML_1_0); } } } } } catch (Exception e) { // Don't stop when there's a problem rendering the title. } return title; } /** * @return the first level 1 or level 1.1 title text in the document's content or "" if none are found * @todo this method has nothing to do in this class and should be moved elsewhere */ private String extractTitle10() { String content = getContent(); Matcher m = HEADING_PATTERN_10.matcher(content); if (m.find()) { return m.group(1).trim(); } return ""; } public void setTitle(String title) { if (title != null && !title.equals(this.title)) { 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); } } /** * @since 3.0M3 */ public DocumentReference getAuthorReference() { return this.authorReference; } /** * @since 3.0M3 */ public void setAuthorReference(DocumentReference authorReference) { this.authorReference = authorReference; } /** * 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() { String author; DocumentReference authorReference = getAuthorReference(); if (authorReference == null) { author = ""; } else { author = this.compactWikiEntityReferenceSerializer.serialize(authorReference, getDocumentReference()); } return author; } /** * 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) { // Note: Consider "" or null as the same, i.e. the author not being set DocumentReference authorReference = null; if (author != null && author.length() > 0) { authorReference = this.explicitReferenceDocumentReferenceResolver.resolve( this.xClassEntityReferenceResolver.resolve(author, EntityType.DOCUMENT), getDocumentReference()); } if ((getAuthorReference() == null && authorReference != null) || (getAuthorReference() != null && !getAuthorReference().equals(authorReference))) { setMetaDataDirty(true); } setAuthorReference(authorReference); } /** * @since 3.0M3 */ public DocumentReference getContentAuthorReference() { return this.contentAuthorReference; } /** * @since 3.0M3 */ public void setContentAuthorReference(DocumentReference contentAuthorReference) { this.contentAuthorReference = contentAuthorReference; } /** * 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() { String contentAuthor; DocumentReference contentAuthorReference = getContentAuthorReference(); if (contentAuthorReference == null) { contentAuthor = ""; } else { contentAuthor = this.compactWikiEntityReferenceSerializer.serialize(contentAuthorReference, getDocumentReference()); } return contentAuthor; } /** * 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) { // Note: Consider "" or null as the same, i.e. the content author not being set DocumentReference contentAuthorReference = null; if (contentAuthor != null && contentAuthor.length() > 0) { contentAuthorReference = this.explicitReferenceDocumentReferenceResolver.resolve( this.xClassEntityReferenceResolver.resolve(contentAuthor, EntityType.DOCUMENT), getDocumentReference()); } if ((getContentAuthorReference() == null && contentAuthorReference != null) || (getContentAuthorReference() != null && !getContentAuthorReference().equals(contentAuthorReference))) { setMetaDataDirty(true); } setContentAuthorReference(contentAuthorReference); } /** * @since 3.0M3 */ public DocumentReference getCreatorReference() { return this.creatorReference; } /** * @since 3.0M3 */ public void setCreatorReference(DocumentReference creatorReference) { this.creatorReference = creatorReference; } /** * 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() { String creator; DocumentReference creatorReference = getCreatorReference(); if (creatorReference == null) { creator = ""; } else { creator = this.compactWikiEntityReferenceSerializer.serialize(creatorReference, getDocumentReference()); } return creator; } /** * 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) { // Note: Consider "" or null as the same, i.e. the creator not being set DocumentReference creatorReference = null; if (creator != null && creator.length() > 0) { creatorReference = this.explicitReferenceDocumentReferenceResolver.resolve( this.xClassEntityReferenceResolver.resolve(creator, EntityType.DOCUMENT), getDocumentReference()); } if ((getCreatorReference() == null && creatorReference != null) || (getCreatorReference() != null && !getCreatorReference().equals(creatorReference))) { setMetaDataDirty(true); } setCreatorReference(creatorReference); } 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) { StringBuffer buf = new StringBuffer(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) { URL url = context.getURLFactory().createAttachmentURL(filename, getSpace(), getName(), action, null, getDatabase(), context); return context.getURLFactory().getURL(url, 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) { URL url = context.getURLFactory().createAttachmentURL(filename, getSpace(), getName(), action, querystring, getDatabase(), context); return context.getURLFactory().getURL(url, context); } public String getAttachmentRevisionURL(String filename, String revision, XWikiContext context) { URL url = context.getURLFactory().createAttachmentRevisionURL(filename, getSpace(), getName(), revision, null, getDatabase(), context); return context.getURLFactory().getURL(url, context); } public String getAttachmentRevisionURL(String filename, String revision, String querystring, XWikiContext context) { URL url = context.getURLFactory().createAttachmentRevisionURL(filename, getSpace(), getName(), revision, querystring, getDatabase(), context); return context.getURLFactory().getURL(url, context); } 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) { if (url == null) { return null; } else { return url.toString(); } } else { return context.getURLFactory().getURL(url, context); } } 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 (int i = 0; i < revisions.length; i++) { nodeinfo = nextNodeinfo; nextNodeinfo = getRevisionInfo(revisions[i].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) { this.xClass = new BaseClass(); this.xClass.setDocumentReference(getDocumentReference()); } return this.xClass; } /** * @deprecated since 2.2M1, use {@link #getXClass()} instead */ @Deprecated public BaseClass getxWikiClass() { return getXClass(); } /** * @since 2.2M1 */ public void setXClass(BaseClass xwikiClass) { xwikiClass.setDocumentReference(getDocumentReference()); this.xClass = xwikiClass; } /** * @deprecated since 2.2M1, use {@link #setXClass(BaseClass)} instead */ @Deprecated public void setxWikiClass(BaseClass xwikiClass) { setXClass(xwikiClass); } /** * @since 2.2M1 */ public Map<DocumentReference, List<BaseObject>> getXObjects() { return this.xObjects; } /** * @deprecated since 2.2M1 use {@link #getXObjects()} instead. Warning: if you used to modify the returned Map note * that since 2.2M1 this will no longer work and you'll need to call the setXObject methods instead (or * setxWikiObjects()). Obviously the best is to move to the new API. */ @Deprecated public Map<String, Vector<BaseObject>> getxWikiObjects() { // Use a liked hash map to ensure we keep the order stored from the internal objects map. Map<String, Vector<BaseObject>> objects = new LinkedHashMap<String, Vector<BaseObject>>(); for (Map.Entry<DocumentReference, List<BaseObject>> entry : getXObjects().entrySet()) { objects.put(this.compactWikiEntityReferenceSerializer.serialize(entry.getKey()), new Vector<BaseObject>(entry.getValue())); } return objects; } /** * @since 2.2M1 */ public void setXObjects(Map<DocumentReference, List<BaseObject>> objects) { this.xObjects = objects; } /** * @deprecated since 2.2M1 use {@link #setXObjects(Map)} instead */ @Deprecated public void setxWikiObjects(Map<String, Vector<BaseObject>> objects) { // Use a liked hash map to ensure we keep the order stored from the internal objects map. Map<DocumentReference, List<BaseObject>> newObjects = new LinkedHashMap<DocumentReference, List<BaseObject>>(); for (Map.Entry<String, Vector<BaseObject>> entry : objects.entrySet()) { newObjects.put(resolveClassReference(entry.getKey()), new ArrayList<BaseObject>(entry.getValue())); } setXObjects(newObjects); } /** * @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; } /** * @deprecated since 2.2M1 use {@link #getXClasses(XWikiContext)} instead */ @Deprecated public List<BaseClass> getxWikiClasses(XWikiContext context) { return getXClasses(context); } /** * @since 2.2.3 */ public int createXObject(EntityReference classReference, XWikiContext context) throws XWikiException { DocumentReference absoluteClassReference = resolveClassReference(classReference); BaseObject object = BaseClass.newCustomClassInstance(absoluteClassReference, context); object.setDocumentReference(getDocumentReference()); object.setXClassReference(classReference); List<BaseObject> objects = getXObjects(absoluteClassReference); if (objects == null) { objects = new ArrayList<BaseObject>(); setXObjects(absoluteClassReference, objects); } objects.add(object); int nb = objects.size() - 1; object.setNumber(nb); setContentDirty(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( this.xClassEntityReferenceResolver.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; } } /** * @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); } /** * @deprecated since 2.2M1 use {@link #getXObjects(DocumentReference)} instead */ @Deprecated public Vector<BaseObject> getObjects(String className) { List<BaseObject> result = getXObjects(resolveClassReference(className)); return result == null ? null : new Vector<BaseObject>(result); } /** * @since 2.2M1 */ public void setXObjects(DocumentReference classReference, List<BaseObject> objects) { if (objects.isEmpty()) { getXObjects().put(classReference, objects); } else { for (BaseObject baseObject : objects) { addXObject(classReference, baseObject); } } } /** * @deprecated since 2.2M1 use {@link #setXObjects(DocumentReference, List)} instead */ @Deprecated public void setObjects(String className, Vector<BaseObject> objects) { setXObjects(resolveClassReference(className), new ArrayList<BaseObject>(objects)); } /** * @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); } return getXObject(baseObjectReference.getXClassReference(), baseObjectReference.getObjectNumber()); } /** * @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) { try { return getXObjects().get(classReference).get(nb); } catch (Exception e) { return null; } } /** * @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); } /** * @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 (int i = 0; i < objects.size(); i++) { BaseObject obj = objects.get(i); 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); } e.printStackTrace(); 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 = getXObjects(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.setDocumentReference(getDocumentReference()); List<BaseObject> vobj = getXObjects(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.setDocumentReference(getDocumentReference()); object.setNumber(nb); } List<BaseObject> objects = getXObjects(classReference); if (objects == null) { objects = new ArrayList<BaseObject>(); setXObjects(classReference, objects); } while (nb >= objects.size()) { objects.add(null); } objects.set(nb, object); setContentDirty(true); } /** * Replaces the object at the specified position and for the specified object 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.setDocumentReference(getDocumentReference()); object.setNumber(nb); List<BaseObject> objects = getXObjects(object.getXClassReference()); if (objects == null) { objects = new ArrayList<BaseObject>(); setXObjects(object.getXClassReference(), objects); } while (nb >= objects.size()) { objects.add(null); } objects.set(nb, object); setContentDirty(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((BaseClass) tbclass.clone()); } else { getXClass().merge((BaseClass) tbclass.clone()); } } setContentDirty(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) { // TODO: look for each object if it already exist and add it if it doesn't for (Map.Entry<DocumentReference, List<BaseObject>> entry : templatedoc.getXObjects().entrySet()) { List<BaseObject> myObjects = getXObjects().get(entry.getKey()); if (myObjects == null) { myObjects = new ArrayList<BaseObject>(); } if (!entry.getValue().isEmpty()) { DocumentReference newXClassReference = null; for (BaseObject otherObject : entry.getValue()) { if (otherObject != null) { BaseObject myObject = otherObject.duplicate(getDocumentReference()); myObjects.add(myObject); myObject.setNumber(myObjects.size() - 1); newXClassReference = myObject.getXClassReference(); } } setXObjects(newXClassReference, myObjects); } } setContentDirty(true); } /** * @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((BaseObject) 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); } } } } /** * @deprecated since 2.2M1 use {@link #cloneXObjects(XWikiDocument)} instead */ @Deprecated public void clonexWikiObjects(XWikiDocument templatedoc) { cloneXObjects(templatedoc); } /** * @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 = this.localEntityReferenceSerializer.serialize(templateDocumentReference); } else { templateReferenceAsString = ""; } return templateReferenceAsString; } /** * @since 2.2M1 */ public void setTemplateDocumentReference(DocumentReference templateDocumentReference) { if ((templateDocumentReference == null && getTemplateDocumentReference() != null) || (templateDocumentReference != null && !templateDocumentReference.equals(getTemplateDocumentReference()))) { 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 = this.currentMixedDocumentReferenceResolver.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 [" + this.defaultEntityReferenceSerializer.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); type = type.toLowerCase(); StringBuffer result = new StringBuffer(); PropertyClass pclass = (PropertyClass) obj.getXClass(context).get(fieldname); String prefix = pref + this.localEntityReferenceSerializer.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")) { // 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 = this.localEntityReferenceSerializer .serialize(obj.getXClass(context).getDocumentReference()) + "_"; pclass.displaySearch(result, fieldname, prefix, (XWikiCriteria) context.get("query"), context); if (is10Syntax(wrappingSyntaxId) && isInRenderingEngine) { result.append("{/pre}"); } } 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 [" + this.defaultEntityReferenceSerializer.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 ""; } StringBuffer result = new StringBuffer(); VelocityContext vcontext = new VelocityContext(); for (String propertyName : bclass.getPropertyList()) { PropertyClass pclass = (PropertyClass) bclass.getField(propertyName); vcontext.put(pclass.getName(), pclass.getPrettyName()); } result.append( XWikiVelocityRenderer.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(XWikiVelocityRenderer.evaluate(format, context.getDoc().getPrefixedFullName(), vcontext, context)); if (linebreak) { result.append("\n"); } } } return result.toString(); } /** * @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 ""; } StringBuffer result = new StringBuffer(); 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); } } /** * 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); return prop != null ? prop.getTextValue() : ""; } 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( this.localEntityReferenceSerializer.serialize(baseclass.getDocumentReference()) + "_" + i), oldobject); newobject.setNumber(oldobject.getNumber()); newobject.setGuid(oldobject.getGuid()); newobject.setDocumentReference(getDocumentReference()); newObjects.set(newobject.getNumber(), newobject); } } getXObjects().put(reference, newObjects); } setContentDirty(true); } public void readFromForm(EditForm eform, XWikiContext context) throws XWikiException { readDocMetaFromForm(eform, context); readTranslationMetaFromForm(eform, context); readObjectsFromForm(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 = { this.defaultEntityReferenceSerializer.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 = { this.defaultEntityReferenceSerializer.serialize(templateDocumentReference), this.compactEntityReferenceSerializer.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); 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.getParentReference()); } if (isNew()) { // We might have received the object 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); } } } setContentDirty(true); } /** * @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 = this.currentMixedDocumentReferenceResolver.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) throws XWikiException { setDocumentReference(document.getDocumentReference()); setRCSVersion(document.getRCSVersion()); setDocumentArchive(document.getDocumentArchive()); setAuthor(document.getAuthor()); setContentAuthor(document.getContentAuthor()); setContent(document.getContent()); setContentDirty(document.isContentDirty()); setCreationDate(document.getCreationDate()); setDate(document.getDate()); setCustomClass(document.getCustomClass()); setContentUpdateDate(document.getContentUpdateDate()); setTitle(document.getTitle()); setFormat(document.getFormat()); setFromCache(document.isFromCache()); setElements(document.getElements()); setId(document.getId()); setMeta(document.getMeta()); setMetaDataDirty(document.isMetaDataDirty()); setMostRecent(document.isMostRecent()); setNew(document.isNew()); setStore(document.getStore()); setTemplateDocumentReference(document.getTemplateDocumentReference()); setParent(document.getParent()); setCreator(document.getCreator()); setDefaultLanguage(document.getDefaultLanguage()); setDefaultTemplate(document.getDefaultTemplate()); setValidationScript(document.getValidationScript()); setLanguage(document.getLanguage()); setTranslation(document.getTranslation()); setXClass((BaseClass) document.getXClass().clone()); setXClassXML(document.getXClassXML()); setComment(document.getComment()); setMinorEdit(document.isMinorEdit()); setSyntax(document.getSyntax()); setHidden(document.isHidden()); cloneXObjects(document); cloneAttachments(document); 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.setDocumentArchive(getDocumentArchive()); doc.setAuthorReference(getAuthorReference()); doc.setContentAuthorReference(getContentAuthorReference()); doc.setContent(getContent()); doc.setContentDirty(isContentDirty()); 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.setId(getId()); doc.setMeta(getMeta()); doc.setMetaDataDirty(isMetaDataDirty()); doc.setMostRecent(isMostRecent()); doc.setNew(isNew()); doc.setStore(getStore()); doc.setTemplateDocumentReference(getTemplateDocumentReference()); doc.setParentReference(getRelativeParentReference()); doc.setCreatorReference(getCreatorReference()); doc.setDefaultLanguage(getDefaultLanguage()); doc.setDefaultTemplate(getDefaultTemplate()); doc.setValidationScript(getValidationScript()); doc.setLanguage(getLanguage()); doc.setTranslation(getTranslation()); doc.setXClass((BaseClass) getXClass().clone()); doc.setXClassXML(getXClassXML()); doc.setComment(getComment()); doc.setMinorEdit(isMinorEdit()); doc.setSyntax(getSyntax()); doc.setHidden(isHidden()); if (keepsIdentity) { doc.cloneXObjects(this); doc.cloneAttachments(this); } else { doc.duplicateXObjects(this); doc.copyAttachments(this); } 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) { getAttachmentList().clear(); Iterator<XWikiAttachment> attit = sourceDocument.getAttachmentList().iterator(); while (attit.hasNext()) { XWikiAttachment attachment = attit.next(); XWikiAttachment newattachment = (XWikiAttachment) attachment.clone(); newattachment.setDoc(this); // TODO: Why must attachment content must be set dirty --cjdelisle if (newattachment.getAttachment_content() != null) { newattachment.getAttachment_content().setContentDirty(true); } getAttachmentList().add(newattachment); } setContentDirty(true); } 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; } XWikiDocument doc = (XWikiDocument) object; if (!getDocumentReference().equals(doc.getDocumentReference())) { return false; } if (!getAuthor().equals(doc.getAuthor())) { return false; } if (!getContentAuthor().equals(doc.getContentAuthor())) { return false; } if ((getParentReference() != null && !getParentReference().equals(doc.getParentReference())) || (getParentReference() == null && doc.getParentReference() != null)) { return false; } if (!getCreator().equals(doc.getCreator())) { return false; } if (!getDefaultLanguage().equals(doc.getDefaultLanguage())) { return false; } if (!getLanguage().equals(doc.getLanguage())) { return false; } if (getTranslation() != doc.getTranslation()) { 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 (!getFormat().equals(doc.getFormat())) { return false; } if (!getTitle().equals(doc.getTitle())) { return false; } if (!getContent().equals(doc.getContent())) { return false; } if (!getVersion().equals(doc.getVersion())) { return false; } if ((getTemplateDocumentReference() != null && !getTemplateDocumentReference().equals(doc.getTemplateDocumentReference())) || (getTemplateDocumentReference() == null && doc.getTemplateDocumentReference() != null)) { return false; } if (!getDefaultTemplate().equals(doc.getDefaultTemplate())) { return false; } if (!getValidationScript().equals(doc.getValidationScript())) { return false; } if (!getComment().equals(doc.getComment())) { return false; } if (isMinorEdit() != doc.isMinorEdit()) { return false; } if ((getSyntaxId() != null && !getSyntaxId().equals(doc.getSyntaxId())) || (getSyntaxId() == null && doc.getSyntaxId() != null)) { return false; } if (isHidden() != doc.isHidden()) { return false; } if (!getXClass().equals(doc.getXClass())) { return false; } Set<DocumentReference> myObjectClassReferences = getXObjects().keySet(); Set<DocumentReference> otherObjectClassReferences = doc.getXObjects().keySet(); if (!myObjectClassReferences.equals(otherObjectClassReferences)) { return false; } for (DocumentReference reference : myObjectClassReferences) { List<BaseObject> myObjects = getXObjects(reference); List<BaseObject> otherObjects = doc.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; } } } // 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; } /** * Convert a {@link Document} into 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 avoid memory load. * * @param doc the {@link Document} to convert to a String * @param context current XWikiContext * @return an XML representation of the {@link Document} * @deprecated this method has nothing to do here and is apparently unused */ @Deprecated public String toXML(Document doc, XWikiContext context) { String encoding = context.getWiki().getEncoding(); ByteArrayOutputStream os = new ByteArrayOutputStream(); try { XMLWriter wr = new XMLWriter(os, new OutputFormat("", true, encoding)); wr.write(doc); return os.toString(encoding); } catch (IOException e) { LOGGER.error("Exception while doc.toXML", e); return ""; } } /** * 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 */ 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 */ 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 */ 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 { ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { toXML(baos, bWithObjects, bWithRendering, bWithAttachmentContent, bWithVersions, context); return baos.toString(context.getWiki().getEncoding()); } catch (IOException e) { e.printStackTrace(); return ""; } } /** * 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 */ 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 */ 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 */ public void toXML(XMLWriter wr, boolean bWithObjects, boolean bWithRendering, boolean bWithAttachmentContent, boolean bWithVersions, XWikiContext context) throws XWikiException, IOException { // IMPORTANT: we don't use SAX apis here because the specified XMLWriter could be a DOMXMLWriter for retro // compatibility reasons Element docel = new DOMElement("xwikidoc"); wr.writeOpen(docel); Element el = new DOMElement("web"); el.addText(getDocumentReference().getLastSpaceReference().getName()); wr.write(el); el = new DOMElement("name"); el.addText(getDocumentReference().getName()); wr.write(el); el = new DOMElement("language"); el.addText(getLanguage()); wr.write(el); el = new DOMElement("defaultLanguage"); el.addText(getDefaultLanguage()); wr.write(el); el = new DOMElement("translation"); el.addText("" + getTranslation()); wr.write(el); el = new DOMElement("parent"); if (getRelativeParentReference() == null) { // No parent have been specified el.addText(""); } else { el.addText(this.defaultEntityReferenceSerializer.serialize(getRelativeParentReference())); } wr.write(el); el = new DOMElement("creator"); el.addText(getCreator()); wr.write(el); el = new DOMElement("author"); el.addText(getAuthor()); wr.write(el); el = new DOMElement("customClass"); el.addText(getCustomClass()); wr.write(el); el = new DOMElement("contentAuthor"); el.addText(getContentAuthor()); wr.write(el); long d = getCreationDate().getTime(); el = new DOMElement("creationDate"); el.addText("" + d); wr.write(el); d = getDate().getTime(); el = new DOMElement("date"); el.addText("" + d); wr.write(el); d = getContentUpdateDate().getTime(); el = new DOMElement("contentUpdateDate"); el.addText("" + d); wr.write(el); el = new DOMElement("version"); el.addText(getVersion()); wr.write(el); el = new DOMElement("title"); el.addText(getTitle()); wr.write(el); el = new DOMElement("template"); if (getTemplateDocumentReference() == null) { // No template doc have been specified el.addText(""); } else { el.addText(this.localEntityReferenceSerializer.serialize(getTemplateDocumentReference())); } wr.write(el); el = new DOMElement("defaultTemplate"); el.addText(getDefaultTemplate()); wr.write(el); el = new DOMElement("validationScript"); el.addText(getValidationScript()); wr.write(el); el = new DOMElement("comment"); el.addText(getComment()); wr.write(el); el = new DOMElement("minorEdit"); el.addText(String.valueOf(isMinorEdit())); wr.write(el); el = new DOMElement("syntaxId"); el.addText(getSyntaxId()); wr.write(el); el = new DOMElement("hidden"); el.addText(String.valueOf(isHidden())); wr.write(el); for (XWikiAttachment attach : getAttachmentList()) { attach.toXML(wr, bWithAttachmentContent, bWithVersions, context); } if (bWithObjects) { // Add Class BaseClass bclass = getXClass(); if (!bclass.getFieldList().isEmpty()) { // If the class has fields, add class definition and field information to XML wr.write(bclass.toXML(null)); } // Add Objects (THEIR ORDER IS MOLDED IN STONE!) for (List<BaseObject> objects : getXObjects().values()) { for (BaseObject obj : objects) { if (obj != null) { BaseClass objclass; if (getDocumentReference() == obj.getXClassReference()) { objclass = bclass; } else { objclass = obj.getXClass(context); } wr.write(obj.toXML(objclass)); } } } } // Add Content el = new DOMElement("content"); // Filter filter = new CharacterFilter(); // String newcontent = filter.process(getContent()); // String newcontent = encodedXMLStringAsUTF8(getContent()); String newcontent = this.content; el.addText(newcontent); wr.write(el); if (bWithRendering) { el = new DOMElement("renderedcontent"); try { el.addText(getRenderedContent(context)); } catch (XWikiException e) { el.addText("Exception with rendering content: " + e.getFullMessage()); } wr.write(el); } if (bWithVersions) { el = new DOMElement("versions"); try { el.addText(getDocumentArchive(context).getArchive(context)); wr.write(el); } catch (XWikiException e) { LOGGER.error("Document [" + this.defaultEntityReferenceSerializer.serialize(getDocumentReference()) + "] has malformed history"); } } } /** * 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 { XMLWriter wr = new XMLWriter(out, new OutputFormat("", true, context.getWiki().getEncoding())); Document doc = new DOMDocument(); wr.writeDocumentStart(doc); toXML(wr, bWithObjects, bWithRendering, bWithAttachmentContent, bWithVersions, context); wr.writeDocumentEnd(doc); } protected String encodedXMLStringAsUTF8(String xmlString) { if (xmlString == null) { return ""; } int length = xmlString.length(); char character; StringBuffer result = new StringBuffer(); 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("&#x"); 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(String xml, boolean withArchive) throws XWikiException { SAXReader reader = new SAXReader(); Document domdoc; try { StringReader in = new StringReader(xml); domdoc = reader.read(in); } catch (DocumentException e) { throw new XWikiException(XWikiException.MODULE_XWIKI_DOC, XWikiException.ERROR_DOC_XML_PARSING, "Error parsing xml", e, null); } fromXML(domdoc, withArchive); } public void fromXML(InputStream in, boolean withArchive) throws XWikiException { SAXReader reader = new SAXReader(); Document domdoc; try { domdoc = reader.read(in); } catch (DocumentException e) { throw new XWikiException(XWikiException.MODULE_XWIKI_DOC, XWikiException.ERROR_DOC_XML_PARSING, "Error parsing xml", e, null); } fromXML(domdoc, withArchive); } public void fromXML(Document domdoc, boolean withArchive) throws XWikiException { Element docel = domdoc.getRootElement(); // If, for some reason, the document name or space are not set in the XML input, we still ensure that the // constructed XWikiDocument object has a valid name or space (by using current document values if they are // missing). This is important since document name, space and wiki must always be set in a XWikiDocument // instance. String name = getElement(docel, "name"); String space = getElement(docel, "web"); if (StringUtils.isEmpty(name) || StringUtils.isEmpty(space)) { throw new XWikiException(XWikiException.MODULE_XWIKI_DOC, XWikiException.ERROR_XWIKI_UNKNOWN, "Invalid XML: \"name\" and \"web\" cannot be empty"); } EntityReference reference = new EntityReference(name, EntityType.DOCUMENT, new EntityReference(space, EntityType.SPACE)); reference = this.currentReferenceDocumentReferenceResolver.resolve(reference); setDocumentReference(new DocumentReference(reference)); String parent = getElement(docel, "parent"); if (StringUtils.isNotEmpty(parent)) { setParentReference(this.currentMixedDocumentReferenceResolver.resolve(parent)); } setCreator(getElement(docel, "creator")); setAuthor(getElement(docel, "author")); setCustomClass(getElement(docel, "customClass")); setContentAuthor(getElement(docel, "contentAuthor")); if (docel.element("version") != null) { setVersion(getElement(docel, "version")); } setContent(getElement(docel, "content")); setLanguage(getElement(docel, "language")); setDefaultLanguage(getElement(docel, "defaultLanguage")); setTitle(getElement(docel, "title")); setDefaultTemplate(getElement(docel, "defaultTemplate")); setValidationScript(getElement(docel, "validationScript")); setComment(getElement(docel, "comment")); String minorEdit = getElement(docel, "minorEdit"); setMinorEdit(Boolean.valueOf(minorEdit).booleanValue()); String hidden = getElement(docel, "hidden"); setHidden(Boolean.valueOf(hidden).booleanValue()); String strans = getElement(docel, "translation"); if ((strans == null) || strans.equals("")) { setTranslation(0); } else { setTranslation(Integer.parseInt(strans)); } String archive = getElement(docel, "versions"); if (withArchive && archive != null && archive.length() > 0) { setDocumentArchive(archive); } String sdate = getElement(docel, "date"); if (!sdate.equals("")) { Date date = new Date(Long.parseLong(sdate)); setDate(date); } String contentUpdateDateString = getElement(docel, "contentUpdateDate"); if (!StringUtils.isEmpty(contentUpdateDateString)) { Date contentUpdateDate = new Date(Long.parseLong(contentUpdateDateString)); setContentUpdateDate(contentUpdateDate); } String scdate = getElement(docel, "creationDate"); if (!scdate.equals("")) { Date cdate = new Date(Long.parseLong(scdate)); setCreationDate(cdate); } String syntaxId = getElement(docel, "syntaxId"); if ((syntaxId == null) || (syntaxId.length() == 0)) { // Documents that don't have syntax ids are considered old documents and thus in // XWiki Syntax 1.0 since newer documents always have syntax ids. setSyntax(Syntax.XWIKI_1_0); } else { setSyntaxId(syntaxId); } List<Element> atels = docel.elements("attachment"); for (Element atel : atels) { XWikiAttachment attach = new XWikiAttachment(); attach.setDoc(this); attach.fromXML(atel); getAttachmentList().add(attach); } Element cel = docel.element("class"); BaseClass bclass = new BaseClass(); if (cel != null) { bclass.fromXML(cel); setXClass(bclass); } @SuppressWarnings("unchecked") List<Element> objels = docel.elements("object"); for (Element objel : objels) { BaseObject bobject = new BaseObject(); bobject.fromXML(objel); setXObject(bobject.getNumber(), bobject); } // We have been reading from XML so the document does not need a new version when saved setMetaDataDirty(false); setContentDirty(false); // Note: We don't set the original document as that is not stored in the XML, and it doesn't make much sense to // have an original document for a de-serialized object. } /** * 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("xwikidoc"); } public void setAttachmentList(List<XWikiAttachment> list) { this.attachmentList = list; } public List<XWikiAttachment> getAttachmentList() { return this.attachmentList; } public void saveAllAttachments(XWikiContext context) throws XWikiException { for (XWikiAttachment attachment : this.attachmentList) { saveAttachmentContent(attachment, context); } } public void saveAllAttachments(boolean updateParent, boolean transaction, XWikiContext context) throws XWikiException { for (XWikiAttachment attachment : this.attachmentList) { saveAttachmentContent(attachment, updateParent, transaction, context); } } public void saveAttachmentsContent(List<XWikiAttachment> attachments, XWikiContext context) throws XWikiException { String database = context.getDatabase(); try { // We might need to switch database to get the translated content if (getDatabase() != null) { context.setDatabase(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.setDatabase(database); } } } public void saveAttachmentContent(XWikiAttachment attachment, XWikiContext context) throws XWikiException { saveAttachmentContent(attachment, true, true, context); } public void saveAttachmentContent(XWikiAttachment attachment, boolean bParentUpdate, boolean bTransaction, XWikiContext context) throws XWikiException { String database = context.getDatabase(); try { // We might need to switch database to // get the translated content if (getDatabase() != null) { context.setDatabase(getDatabase()); } // We need to make sure there is a version upgrade setMetaDataDirty(true); context.getWiki().getAttachmentStore().saveAttachmentContent(attachment, bParentUpdate, context, bTransaction); } 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.setDatabase(database); } } } public void loadAttachmentContent(XWikiAttachment attachment, XWikiContext context) throws XWikiException { String database = context.getDatabase(); try { // We might need to switch database to // get the translated content if (getDatabase() != null) { context.setDatabase(getDatabase()); } context.getWiki().getAttachmentStore().loadAttachmentContent(attachment, context, true); } finally { if (database != null) { context.setDatabase(database); } } } public void deleteAttachment(XWikiAttachment attachment, XWikiContext context) throws XWikiException { deleteAttachment(attachment, true, context); } public void deleteAttachment(XWikiAttachment attachment, boolean toRecycleBin, XWikiContext context) throws XWikiException { String database = context.getDatabase(); try { // We might need to switch database to // get the translated content if (getDatabase() != null) { context.setDatabase(getDatabase()); } try { // We need to make sure there is a version upgrade setMetaDataDirty(true); if (toRecycleBin && context.getWiki().hasAttachmentRecycleBin(context)) { context.getWiki().getAttachmentRecycleBinStore().saveToRecycleBin(attachment, context.getUser(), new Date(), context, true); } context.getWiki().getAttachmentStore().deleteXWikiAttachment(attachment, context, true); } catch (java.lang.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.setDatabase(database); } } } /** * 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. * <p> * <ul> * <li>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 1.0 content. * @since 1.9M2 */ public Set<XWikiLink> getUniqueWikiLinkedPages(XWikiContext context) throws XWikiException { Set<XWikiLink> links; if (is10Syntax()) { links = new LinkedHashSet<XWikiLink>(getStore(context).loadLinks(getId(), context, true)); } else { Set<String> linkedPages = getUniqueLinkedPages(context); links = new LinkedHashSet<XWikiLink>(linkedPages.size()); for (String linkedPage : linkedPages) { XWikiLink wikiLink = new XWikiLink(); wikiLink.setDocId(getId()); wikiLink.setFullName(this.localEntityReferenceSerializer.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 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 = this.currentDocumentReferenceResolver.resolve(name); // Verify that the link is not an autolink (i.e. a link to the current document) if (!documentReference.equals(currentDocumentReference)) { pageNames.add(this.compactEntityReferenceSerializer.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.getDatabase(); 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.setDatabase(getDatabase()); if (is10Syntax()) { pageNames = getUniqueLinkedPages10(context); } else { XDOM dom = getXDOM(); List<LinkBlock> linkBlocks = dom.getChildrenByType(LinkBlock.class, true); pageNames = new LinkedHashSet<String>(linkBlocks.size()); DocumentReference currentDocumentReference = getDocumentReference(); for (LinkBlock linkBlock : linkBlocks) { ResourceReference reference = linkBlock.getReference(); if (reference.getType().equals(ResourceType.DOCUMENT)) { // If the reference is empty, the link is an autolink if (!StringUtils.isEmpty(reference.getReference()) || (StringUtils.isEmpty(reference.getParameter(DocumentResourceReference.ANCHOR)) && StringUtils.isEmpty( reference.getParameter(DocumentResourceReference.QUERY_STRING)))) { // 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 = this.currentDocumentReferenceResolver .resolve(reference.getReference()); // Verify 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( this.compactWikiEntityReferenceSerializer.serialize(documentReference)); } } } } } } finally { context.setDoc(contextDoc); context.setDatabase(contextWiki); } return pageNames; } /** * Returns a list of references of all documents which list this document as their parent * {@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 * * @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<String> params = Arrays.asList(this.defaultEntityReferenceSerializer.serialize(getDocumentReference()), this.localEntityReferenceSerializer.serialize(getDocumentReference()), getDocumentReference().getName(), getDocumentReference().getLastSpaceReference().getName()); String whereStatement = "doc.parent=? or doc.parent=? or (doc.parent=? and doc.space=?)"; return context.getWiki().getStore().searchDocumentReferences(whereStatement, nb, start, params, context); } /** * @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(this.localEntityReferenceSerializer.serialize(reference)); } return childrenNames; } /** * @since 2.2M2 */ public void renameProperties(DocumentReference classReference, Map<String, String> fieldsToRename) { List<BaseObject> objects = getXObjects(classReference); if (objects == null) { return; } 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 = (BaseProperty) origprop.clone(); bobject.removeField(origname); prop.setName(newname); bobject.addField(newname, prop); } } } setContentDirty(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); setContentDirty(true); } /** * @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; setContentDirty(true); } /** * @deprecated since 2.2M2 use {@link #setXObjectsToRemove(List)} instead */ @Deprecated public void setObjectsToRemove(ArrayList<BaseObject> objectsToRemove) { setXObjectsToRemove(objectsToRemove); } public List<String> getIncludedPages(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>(); for (MacroBlock macroBlock : dom.getChildrenByType(MacroBlock.class, true)) { // - 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")) { String documentName = macroBlock.getParameters().get("document"); if (documentName.indexOf('.') == -1) { documentName = getSpace() + "." + documentName; } 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); } public String displaySearch(PropertyClass pclass, String prefix, XWikiCriteria criteria, XWikiContext context) { return (pclass == null) ? "" : pclass.displaySearch(pclass.getName(), prefix, criteria, context); } 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; } public XWikiAttachment addAttachment(String fileName, InputStream iStream, XWikiContext context) throws XWikiException, IOException { ByteArrayOutputStream bAOut = new ByteArrayOutputStream(); IOUtils.copy(iStream, bAOut); return addAttachment(fileName, bAOut.toByteArray(), context); } public XWikiAttachment addAttachment(String fileName, byte[] data, XWikiContext context) throws XWikiException { int i = fileName.indexOf('\\'); if (i == -1) { i = fileName.indexOf('/'); } String filename = fileName.substring(i + 1); // TODO : avoid name clearing when encoding problems will be solved // JIRA : http://jira.xwiki.org/jira/browse/XWIKI-94 filename = context.getWiki().clearName(filename, false, true, context); XWikiAttachment attachment = getAttachment(filename); if (attachment == null) { attachment = new XWikiAttachment(); // TODO: Review this code and understand why it's needed. // Add the attachment in the current doc getAttachmentList().add(attachment); } attachment.setContent(data); attachment.setFilename(filename); attachment.setAuthor(context.getUser()); // Add the attachment to the document attachment.setDoc(this); 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( this.xClassEntityReferenceResolver.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 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( this.xClassEntityReferenceResolver.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( this.xClassEntityReferenceResolver.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( this.xClassEntityReferenceResolver.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( this.xClassEntityReferenceResolver.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( this.xClassEntityReferenceResolver.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) { getDocumentReference().getWikiReference().setName(database); // Clean the absolute parent reference cache to rebuild it next time getParentReference is called. this.parentReferenceCache = null; } } public String getLanguage() { if (this.language == null) { return ""; } else { return this.language.trim(); } } public void setLanguage(String language) { this.language = Util.normalizeLanguage(language); } public String getDefaultLanguage() { if (this.defaultLanguage == null) { return ""; } else { return this.defaultLanguage.trim(); } } public void setDefaultLanguage(String defaultLanguage) { this.defaultLanguage = defaultLanguage; setMetaDataDirty(true); } public int getTranslation() { return this.translation; } public void setTranslation(int translation) { this.translation = translation; setMetaDataDirty(true); } public String getTranslatedContent(XWikiContext context) throws XWikiException { String language = context.getWiki().getLanguagePreference(context); return getTranslatedContent(language, context); } public String getTranslatedContent(String language, XWikiContext context) throws XWikiException { XWikiDocument tdoc = getTranslatedDocument(language, context); return tdoc.getContent(); } public XWikiDocument getTranslatedDocument(XWikiContext context) throws XWikiException { String language = context.getWiki().getLanguagePreference(context); return getTranslatedDocument(language, context); } public XWikiDocument getTranslatedDocument(String language, XWikiContext context) throws XWikiException { XWikiDocument tdoc = this; if (!((language == null) || (language.equals("")) || language.equals(getDefaultLanguage()))) { tdoc = new XWikiDocument(getDocumentReference()); tdoc.setLanguage(language); String database = context.getDatabase(); try { // We might need to switch database to // get the translated content if (getDatabase() != null) { context.setDatabase(getDatabase()); } tdoc = getStore(context).loadXWikiDoc(tdoc, context); if (tdoc.isNew()) { tdoc = this; } } catch (Exception e) { tdoc = this; } finally { context.setDatabase(database); } } return tdoc; } public String getRealLanguage(XWikiContext context) throws XWikiException { return getRealLanguage(); } public String getRealLanguage() { String lang = getLanguage(); if ((lang.equals("") || lang.equals("default"))) { return getDefaultLanguage(); } else { return lang; } } public List<String> getTranslationList(XWikiContext context) throws XWikiException { return getStore().getTranslationList(this, context); } 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, newContent; originalContent = context.getWiki().getRenderingEngine().renderText(fromDoc.getContent(), fromDoc, context); newContent = context.getWiki().getRenderingEngine().renderText(toDoc.getContent(), toDoc, 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 (!fromDoc.getParent().equals(toDoc.getParent())) { list.add(new MetaDataDiff("parent", fromDoc.getParent(), toDoc.getParent())); } if (!fromDoc.getAuthor().equals(toDoc.getAuthor())) { list.add(new MetaDataDiff("author", fromDoc.getAuthor(), toDoc.getAuthor())); } 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 (!fromDoc.getLanguage().equals(toDoc.getLanguage())) { list.add(new MetaDataDiff("language", fromDoc.getLanguage(), toDoc.getLanguage())); } if (!fromDoc.getDefaultLanguage().equals(toDoc.getDefaultLanguage())) { list.add(new MetaDataDiff("defaultLanguage", fromDoc.getDefaultLanguage(), toDoc.getDefaultLanguage())); } 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.getXClassReference()); 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 * @throws XWikiException */ 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, origAttach.getVersion(), null)); } else { if (!origAttach.getVersion().equals(newAttach.getVersion())) { difflist.add(new AttachmentDiff(fileName, origAttach.getVersion(), newAttach.getVersion())); } } } for (XWikiAttachment newAttach : toDoc.getAttachmentList()) { String fileName = newAttach.getFilename(); XWikiAttachment origAttach = fromDoc.getAttachment(fileName); if (origAttach == null) { difflist.add(new AttachmentDiff(fileName, null, newAttach.getVersion())); } } 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(String, 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); childDocument.setParentReference(newDocumentReference); String saveMessage = context.getMessageTool().get("core.comment.renameParent", Arrays.asList(this.compactEntityReferenceSerializer.serialize(newDocumentReference))); 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. // Note: we ignore invalid links here. Invalid links should be shown to the user so // that they fix them but the rename feature ignores them. DocumentParser documentParser = new DocumentParser(); // This link handler recognizes that 2 links are the same when they point to the same // document (regardless of query string, target or alias). It keeps the query string, // target and alias from the link being replaced. RenamePageReplaceLinkHandler linkHandler = new RenamePageReplaceLinkHandler(); // Used for replacing links in XWiki Syntax 1.0 Link oldLink = createLink(getDocumentReference()); Link newLink = createLink(newDocumentReference); for (DocumentReference backlinkDocumentReference : backlinkDocumentReferences) { XWikiDocument backlinkDocument = xwiki.getDocument(backlinkDocumentReference, context); if (backlinkDocument.is10Syntax()) { // Note: Here we cannot do a simple search/replace as there are several ways to point // to the same document. For example [Page], [Page?param=1], [currentwiki:Page], // [CurrentSpace.Page] all point to the same document. Thus we have to parse the links // to recognize them and do the replace. ReplacementResultCollection result = documentParser.parseLinksAndReplace( backlinkDocument.getContent(), oldLink, newLink, linkHandler, getDocumentReference().getLastSpaceReference().getName()); backlinkDocument.setContent((String) result.getModifiedContent()); } else if (Utils.getComponentManager().hasComponent(BlockRenderer.class, backlinkDocument.getSyntax().toIdString())) { backlinkDocument.refactorDocumentLinks(getDocumentReference(), newDocumentReference, context); } String saveMessage = context.getMessageTool().get("core.comment.renameLink", Arrays.asList(this.compactEntityReferenceSerializer.serialize(newDocumentReference))); xwiki.saveDocument(backlinkDocument, saveMessage, true, context); } // Get new document XWikiDocument newDocument = xwiki.getDocument(newDocumentReference, context); // Step 4: Refactor the links contained in the document if (Utils.getComponentManager().hasComponent(BlockRenderer.class, getSyntax().toIdString())) { // Only support syntax for which a renderer is provided XDOM newDocumentXDOM = newDocument.getXDOM(); List<LinkBlock> linkBlockList = newDocumentXDOM.getChildrenByType(LinkBlock.class, true); boolean modified = false; for (LinkBlock linkBlock : linkBlockList) { ResourceReference linkReference = linkBlock.getReference(); if (linkReference.getType().equals(ResourceType.DOCUMENT)) { DocumentReference currentLinkReference = this.explicitDocumentReferenceResolver .resolve(linkReference.getReference(), getDocumentReference()); DocumentReference newLinkReference = this.explicitDocumentReferenceResolver .resolve(linkReference.getReference(), newDocument.getDocumentReference()); if (!newLinkReference.equals(currentLinkReference)) { modified = true; linkReference.setReference(this.compactWikiEntityReferenceSerializer .serialize(currentLinkReference, newDocument.getDocumentReference())); } } } // Set new content and save document if needed if (modified) { newDocument.setContent(newDocumentXDOM); 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); } /** * Generate a {@link Link} object from {@link DocumentReference} to be used in * {@link DocumentParser#parseLinksAndReplace(String, Link, Link, com.xpn.xwiki.content.parsers.ReplaceLinkHandler, String)} * * @param documentReference the full document reference * @return a {@link Link} */ private Link createLink(DocumentReference documentReference) { Link link = new Link(); link.setVirtualWikiAlias(documentReference.getWikiReference().getName()); link.setSpace(documentReference.getLastSpaceReference().getName()); link.setPage(documentReference.getName()); return link; } /** * @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(this.currentMixedDocumentReferenceResolver.resolve(backlinkDocumentName)); } List<DocumentReference> childDocumentReferences = new ArrayList<DocumentReference>(); for (String childDocumentName : childDocumentNames) { childDocumentReferences.add(this.currentMixedDocumentReferenceResolver.resolve(childDocumentName)); } rename(this.currentMixedDocumentReferenceResolver.resolve(newDocumentName), backlinkDocumentReferences, childDocumentReferences, context); } /** * @since 2.2M1 */ private void refactorDocumentLinks(DocumentReference oldDocumentReference, DocumentReference newDocumentReference, XWikiContext context) throws XWikiException { XDOM xdom = getXDOM(); List<LinkBlock> linkBlockList = xdom.getChildrenByType(LinkBlock.class, true); for (LinkBlock linkBlock : linkBlockList) { ResourceReference linkReference = linkBlock.getReference(); if (linkReference.getType().equals(ResourceType.DOCUMENT)) { DocumentReference documentReference = this.explicitDocumentReferenceResolver .resolve(linkReference.getReference(), getDocumentReference()); if (documentReference.equals(oldDocumentReference)) { linkReference.setReference(this.compactEntityReferenceSerializer.serialize(newDocumentReference, getDocumentReference())); } } } setContent(xdom); } /** * @since 2.2M1 */ public XWikiDocument copyDocument(DocumentReference newDocumentReference, XWikiContext context) throws XWikiException { loadAttachments(context); loadArchive(context); XWikiDocument newdoc = duplicate(newDocumentReference); newdoc.setOriginalDocument(null); newdoc.setContentDirty(true); newdoc.getXClass().setDocumentReference(newDocumentReference); XWikiDocumentArchive archive = newdoc.getDocumentArchive(); if (archive != null) { newdoc.setDocumentArchive(archive.clone(newdoc.getId(), context)); } return newdoc; } /** * @deprecated since 2.2M1 use {@link #copyDocument(DocumentReference, XWikiContext)} instead */ @Deprecated public XWikiDocument copyDocument(String newDocumentName, XWikiContext context) throws XWikiException { return copyDocument(this.currentMixedDocumentReferenceResolver.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 */ @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 */ @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 if an error happens when computing the edit mode */ public String getDefaultEditMode(XWikiContext context) throws XWikiException { String editModeProperty = "defaultEditMode"; DocumentReference editModeClass = currentReferenceDocumentReferenceResolver .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 macro 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)) { String documentName = macroBlock.getParameter("document"); if (documentName != null) { // Resolve the document name into a valid Reference DocumentReference documentReference = this.currentMixedDocumentReferenceResolver .resolve(documentName); 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.getObject(XWikiConstant.SHEET_CLASS); } 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) { StringBuffer editparams = new StringBuffer(); 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 */ 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. * * @see org.xwiki.bridge.DocumentModelBridge#getSyntaxId() * @deprecated since 2.3M1, use {link #getSyntax()} instead */ @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) { this.syntax = syntax; } /** * 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 (eg "xwiki/1.0", "xwiki/2.0", 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 = this.syntaxFactory.createSyntaxFromIdString(syntaxId); } catch (ParseException e) { syntax = getDefaultDocumentSyntax(); LOGGER.warn("Failed to set syntax [" + syntaxId + "] for [" + this.defaultEntityReferenceSerializer.serialize(getDocumentReference()) + "], setting syntax [" + syntax.toIdString() + "] instead.", e); } } setSyntax(syntax); } public Vector<BaseObject> getComments(boolean asc) { Vector<BaseObject> list = getObjects("XWiki.XWikiComments"); if (asc) { return list; } else { if (list == null) { return list; } 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.getUser()); } public boolean isCreator(String username) { if (username.equals(XWikiRightService.GUEST_USER_FULLNAME)) { return false; } return username.equals(getCreator()); } public boolean isCurrentUserPage(XWikiContext context) { DocumentReference userReference = context.getUserReference(); if (userReference == null) { return false; } return userReference.equals(getDocumentReference()); } public boolean isCurrentLocalUserPage(XWikiContext context) { String username = context.getLocalUser(); if (username.equals(XWikiRightService.GUEST_USER_FULLNAME)) { return false; } return context.getUser().equals(getFullName()); } 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((HttpServletRequest) context.getRequest()); form.readRequest(); EntityReference classReference = this.xClassEntityReferenceResolver.resolve(form.getClassName(), EntityType.DOCUMENT, getDocumentReference()); BaseObject object = newXObject(classReference, context); BaseClass baseclass = object.getXClass(context); baseclass.fromMap( form.getObject( this.localEntityReferenceSerializer.serialize(resolveClassReference(classReference))), object); return object; } /** * @deprecated since 2.2M2 use {@link #addXObjectFromRequest(XWikiContext)} */ @Deprecated public BaseObject addObjectFromRequest(XWikiContext context) throws XWikiException { return addXObjectFromRequest(context); } /** * 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 + this.localEntityReferenceSerializer.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 + this.localEntityReferenceSerializer.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 + this.localEntityReferenceSerializer.serialize(absoluteClassReference) + "_" + nb; BaseObject newobject = (BaseObject) baseclass.fromMap(Util.getObject(context.getRequest(), newPrefix), oldobject); newobject.setNumber(oldobject.getNumber()); newobject.setGuid(oldobject.getGuid()); newobject.setDocumentReference(getDocumentReference()); 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( this.xClassEntityReferenceResolver.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 + this.localEntityReferenceSerializer.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( this.xClassEntityReferenceResolver.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 (int i = 0; i < matches.length; i++) { if (content2.indexOf(matches[i].toLowerCase()) != -1) { return true; } } if (HTML_TAG_PATTERN.matcher(content2).find()) { return true; } return false; } public boolean isProgrammaticContent() { String[] matches = { "<%", "\\$xwiki.xWiki", "$context.context", "$doc.document", "$xwiki.getXWiki()", "$context.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 (int i = 0; i < matches.length; i++) { if (content2.indexOf(matches[i].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 = getXObjects(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 addObjectsToRemove(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 = getXObjects(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) { addObjectsToRemove(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 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() { Execution execution = Utils.getComponent(Execution.class); ExecutionContext ec = execution.getContext(); XWikiContext context = null; if (ec != null) { context = (XWikiContext) ec.getProperty("xwikicontext"); } return context; } /** * 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 headers List<HeaderBlock> headers = getXDOM().getChildrenByType(HeaderBlock.class, true); // get the maximum header level int sectionDepth = 2; XWikiContext context = getXWikiContext(); if (context != null) { sectionDepth = (int) context.getWiki().getSectionEditingDepth(); } // filter the headers for (HeaderBlock header : headers) { if (header.getLevel().getAsInt() <= sectionDepth) { filteredHeaders.add(header); } } 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 { StringBuffer newContent = new StringBuffer(); // 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(); StringBuffer sb = new StringBuffer(); for (int j = 0; j < array.length; ++j) { int b = array[j] & 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) { XWikiMessageTool msg = context.getMessageTool(); String cpropname = StringUtils.capitalize(propname); return (msg == null) ? cpropname : msg.get(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()); } /** * @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( this.xClassEntityReferenceResolver.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; } } /** * @deprecated since 2.2M2 use {@link #getXObject(DocumentReference, boolean, XWikiContext)} */ @Deprecated public BaseObject getObject(String className, boolean create, XWikiContext context) { return getXObject(resolveClassReference(className), 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 (int i = 0; i < classNames.length; i++) { List<BaseObject> objects = getXObjects( this.currentMixedDocumentReferenceResolver.resolve(classNames[i])); 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) { backup.put("doc", context.getDoc()); VelocityManager velocityManager = Utils.getComponent(VelocityManager.class); VelocityContext vcontext = velocityManager.getVelocityContext(); if (vcontext != null) { backup.put("vdoc", vcontext.get("doc")); backup.put("vcdoc", vcontext.get("cdoc")); backup.put("vtdoc", vcontext.get("tdoc")); } @SuppressWarnings("unchecked") Map<String, Object> gcontext = (Map<String, Object>) context.get("gcontext"); if (gcontext != null) { backup.put("gdoc", gcontext.get("doc")); backup.put("gcdoc", gcontext.get("cdoc")); backup.put("gtdoc", gcontext.get("tdoc")); } // Clone the Execution Context to provide isolation Execution execution = Utils.getComponent(Execution.class); ExecutionContext clonedEc; try { clonedEc = Utils.getComponent(ExecutionContextManager.class).clone(execution.getContext()); } catch (ExecutionContextException e) { throw new RuntimeException("Failed to clone the Execution Context", e); } execution.pushContext(clonedEc); } public static void restoreContext(Map<String, Object> backup, XWikiContext context) { // Restore the Execution Context Execution execution = Utils.getComponent(Execution.class); execution.popContext(); @SuppressWarnings("unchecked") Map<String, Object> gcontext = (Map<String, Object>) context.get("gcontext"); if (gcontext != null) { if (backup.get("gdoc") != null) { gcontext.put("doc", backup.get("gdoc")); } if (backup.get("gcdoc") != null) { gcontext.put("cdoc", backup.get("gcdoc")); } if (backup.get("gtdoc") != null) { gcontext.put("tdoc", backup.get("gtdoc")); } } VelocityManager velocityManager = Utils.getComponent(VelocityManager.class); VelocityContext vcontext = velocityManager.getVelocityContext(); if (vcontext != null) { if (backup.get("vdoc") != null) { vcontext.put("doc", backup.get("vdoc")); } if (backup.get("vcdoc") != null) { vcontext.put("cdoc", backup.get("vcdoc")); } if (backup.get("vtdoc") != null) { vcontext.put("tdoc", backup.get("vtdoc")); } } if (backup.get("doc") != null) { context.setDoc((XWikiDocument) backup.get("doc")); } } public void setAsContextDoc(XWikiContext context) { try { context.setDoc(this); com.xpn.xwiki.api.Document apidoc = newDocument(context); com.xpn.xwiki.api.Document tdoc = apidoc.getTranslatedDocument(); VelocityManager velocityManager = Utils.getComponent(VelocityManager.class); VelocityContext vcontext = velocityManager.getVelocityContext(); @SuppressWarnings("unchecked") Map<String, Object> gcontext = (Map<String, Object>) context.get("gcontext"); if (vcontext != null) { vcontext.put("doc", apidoc); vcontext.put("tdoc", tdoc); } if (gcontext != null) { gcontext.put("doc", apidoc); gcontext.put("tdoc", tdoc); } } catch (XWikiException ex) { LOGGER.warn("Unhandled exception setting context", ex); } } /** * @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. 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. * * @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. 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. * * @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 (eg "xwiki/2.0", "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(this.syntaxFactory.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 (eg "xwiki/2.0", "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(), getSyntaxId(), 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(), getSyntaxId(), targetSyntax)); } } } } } } // change syntax setSyntax(targetSyntax); } /** * @return the XDOM corresponding to the document's string content. */ public XDOM getXDOM() { if (this.xdom == null) { try { this.xdom = parseContent(getContent()); } catch (XWikiException e) { LOGGER.error("Failed to parse document content to XDOM", e); } } return this.xdom.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.getComponent(DocumentReferenceResolver.class).resolve("")); } 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.language = ""; this.defaultLanguage = ""; this.attachmentList = new ArrayList<XWikiAttachment>(); 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) throws XWikiException { try { 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 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(String content, String source, Syntax targetSyntax, TransformationContext txContext) throws XWikiException { try { XDOM dom = parseContent(txContext.getSyntax().toIdString(), content); // Set the source metadata for the parsed XDOM so that Renderers can resolve relative links/images based // on it. dom.getMetaData().addMetaData(MetaData.SOURCE, source); return performSyntaxConversion(dom, targetSyntax, txContext); } 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 content to convert * @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, String currentSyntaxId, Syntax targetSyntax) throws XWikiException { try { XDOM dom = parseContent(currentSyntaxId, content); 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 */ private 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(getSyntaxId(), content); } private static XDOM parseContent(String syntaxId, String content) throws XWikiException { try { Parser parser = Utils.getComponent(Parser.class, syntaxId); return parser.parse(new StringReader(content)); } catch (ParseException e) { throw new XWikiException(XWikiException.MODULE_XWIKI_RENDERING, XWikiException.ERROR_XWIKI_UNKNOWN, "Failed to parse content of syntax [" + syntaxId + "]", e); } } private Syntax getDefaultDocumentSyntax() { // If there's no parser available for the specified syntax default to XWiki 2.0 syntax Syntax syntax = Utils.getComponent(CoreConfiguration.class).getDefaultDocumentSyntax(); try { Utils.getComponent(Parser.class, syntax.toIdString()); } catch (Exception e) { LOGGER.warn("Failed to find parser for the default syntax [" + syntax.toIdString() + "]. Defaulting to xwiki/2.0 syntax."); syntax = Syntax.XWIKI_2_0; } return syntax; } private String serializeReference(DocumentReference reference, EntityReferenceSerializer<String> serializer, DocumentReference defaultReference) { XWikiContext xcontext = getXWikiContext(); String originalWikiName = xcontext.getDatabase(); XWikiDocument originalCurentDocument = xcontext.getDoc(); try { xcontext.setDatabase(defaultReference.getWikiReference().getName()); xcontext.setDoc(new XWikiDocument(defaultReference)); return serializer.serialize(reference); } finally { xcontext.setDoc(originalCurentDocument); xcontext.setDatabase(originalWikiName); } } /** * 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 this.explicitDocumentReferenceResolver.resolve(documentName, defaultReference); } /** * Transforms a XClass reference relative to this document into an absolute reference. */ private DocumentReference resolveClassReference(EntityReference reference) { DocumentReference defaultReference = new DocumentReference( getDocumentReference().getWikiReference().getName(), XWiki.SYSTEM_SPACE, getDocumentReference().getName()); return this.explicitReferenceDocumentReferenceResolver.resolve(reference, defaultReference); } /** * @return the relative parent reference, this method should stay private since this the relative saving of the * parent reference is an implementation detail and from the outside we should only see absolute references * @since 2.2.3 */ protected 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()); setContentDirty(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. * <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 previousDocument the previous version of the document * @param newDocument the next version of the document * @param configuration the configuration of the merge Indicate 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.mergeString(previousDocument.getTitle(), newDocument.getTitle(), getTitle(), mergeResult)); // Content setContent(MergeUtils.mergeString(previousDocument.getContent(), newDocument.getContent(), getContent(), mergeResult)); // Parent if (!ObjectUtils.equals(previousDocument.getAuthorReference(), newDocument.getAuthorReference())) { if (ObjectUtils.equals(previousDocument.getAuthorReference(), getAuthorReference())) { setParentReference(newDocument.getRelativeParentReference()); mergeResult.setModified(true); } } // Author if (!ObjectUtils.equals(previousDocument.getAuthorReference(), newDocument.getAuthorReference()) && ObjectUtils.equals(previousDocument.getAuthorReference(), getAuthorReference())) { setAuthorReference(newDocument.getAuthorReference()); mergeResult.setModified(true); } // 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 { // XXX: collision between DB and new: object to add but already exists in the DB mergeResult.getErrors().add(new CollisionException( "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 { // XXX: collision between DB and new: object to remove but not the same as previous // version mergeResult.getErrors().add(new CollisionException( "Collision found on object [" + objectResult.getReference() + "]")); } } else { // Already removed from DB, lets assume the user is prescient mergeResult.getWarnings().add(new CollisionException( "Object [" + previousObject.getReference() + "] already removed")); } } else if (previousObject != null && newObject != null) { if (diff.getAction() == ObjectDiff.ACTION_PROPERTYADDED) { if (propertyResult == null) { objectResult.addField(diff.getPropName(), newProperty); mergeResult.setModified(true); } else { // XXX: collision between DB and new: property to add but already exists in the DB mergeResult.getErrors() .add(new CollisionException("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 { // XXX: collision between DB and new: supposed to be removed but the DB version is // not the same as the previous version mergeResult.getErrors() .add(new CollisionException("Collision found on object property [" + propertyResult.getReference() + "]")); } } else { // Already removed from DB, lets assume the user is prescient mergeResult.getWarnings().add(new CollisionException("Object property [" + previousProperty.getReference() + "] already removed")); } } else if (diff.getAction() == ObjectDiff.ACTION_PROPERTYCHANGED) { if (propertyResult != null) { if (propertyResult.equals(previousProperty)) { objectResult.addField(diff.getPropName(), newProperty); mergeResult.setModified(true); } else { // TODO: try to apply a 3 ways merge on the property } } else { // XXX: collision between DB and new: property to modify but does not exists in DB // Lets assume it's a mistake to fix mergeResult.getWarnings().add(new CollisionException( "Object [" + newProperty.getReference() + "] does not exists")); objectResult.addField(diff.getPropName(), newProperty); mergeResult.setModified(true); } } } } } } // 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) { if (diff.getNewVersion() == null) { try { XWikiAttachment attachmentResult = getAttachment(diff.getFileName()); deleteAttachment(attachmentResult, context); mergeResult.setModified(true); } catch (XWikiException e) { mergeResult.getErrors().add(e); } } } } return mergeResult; } }