1. Project Clover database Sat Feb 2 2019 06:45:20 CET
  2. Package com.xpn.xwiki.doc

File XWikiDocumentArchive.java

 

Coverage histogram

../../../../img/srcFileCovDistChart10.png
0% of files have more coverage

Code metrics

42
152
28
1
492
300
55
0.36
5.43
28
1.96

Classes

Class Line # Actions
XWikiDocumentArchive 49 152 0% 55 12
0.945945994.6%
 

Contributing tests

This file is covered by 11 tests. .

Source view

1    /*
2    * See the NOTICE file distributed with this work for additional
3    * information regarding copyright ownership.
4    *
5    * This is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU Lesser General Public License as
7    * published by the Free Software Foundation; either version 2.1 of
8    * the License, or (at your option) any later version.
9    *
10    * This software is distributed in the hope that it will be useful,
11    * but WITHOUT ANY WARRANTY; without even the implied warranty of
12    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13    * Lesser General Public License for more details.
14    *
15    * You should have received a copy of the GNU Lesser General Public
16    * License along with this software; if not, write to the Free
17    * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18    * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19    */
20    package com.xpn.xwiki.doc;
21   
22    import java.util.ArrayList;
23    import java.util.Collection;
24    import java.util.Date;
25    import java.util.Iterator;
26    import java.util.List;
27    import java.util.Set;
28    import java.util.SortedMap;
29    import java.util.SortedSet;
30    import java.util.TreeMap;
31    import java.util.TreeSet;
32   
33    import org.suigeneris.jrcs.rcs.Version;
34    import org.suigeneris.jrcs.util.ToString;
35   
36    import com.xpn.xwiki.XWikiContext;
37    import com.xpn.xwiki.XWikiException;
38    import com.xpn.xwiki.doc.rcs.XWikiPatch;
39    import com.xpn.xwiki.doc.rcs.XWikiRCSArchive;
40    import com.xpn.xwiki.doc.rcs.XWikiRCSNodeContent;
41    import com.xpn.xwiki.doc.rcs.XWikiRCSNodeId;
42    import com.xpn.xwiki.doc.rcs.XWikiRCSNodeInfo;
43   
44    /**
45    * Contains document history. Allows to load any version of document.
46    *
47    * @version $Id: 39ce29aec8790059ac114987bea006b896dd7dac $
48    */
 
49    public class XWikiDocumentArchive
50    {
51    /** =docId. */
52    private long id;
53   
54    /** SortedMap from Version to XWikiRCSNodeInfo. */
55    private SortedMap<Version, XWikiRCSNodeInfo> versionToNode = new TreeMap<Version, XWikiRCSNodeInfo>();
56   
57    /**
58    * SortedSet of Version - versions which has full document, not patch. Latest version is always full.
59    */
60    private SortedSet<Version> fullVersions = new TreeSet<Version>();
61   
62    // store-specific information
63    /** Set of {@link XWikiRCSNodeInfo} which need to delete. */
64    private Set<XWikiRCSNodeInfo> deletedNodes = new TreeSet<XWikiRCSNodeInfo>();
65   
66    /** Set of {@link XWikiRCSNodeInfo} which need to saveOrUpdate. */
67    private Set<XWikiRCSNodeInfo> updatedNodeInfos = new TreeSet<XWikiRCSNodeInfo>();
68   
69    /** Set of {@link XWikiRCSNodeContent} which need to update. */
70    private Set<XWikiRCSNodeContent> updatedNodeContents = new TreeSet<XWikiRCSNodeContent>();
71   
72    /** @param id = {@link XWikiDocument#getId()} */
 
73  8416 toggle public XWikiDocumentArchive(long id)
74    {
75  8416 this();
76  8416 setId(id);
77    }
78   
79    /** default constructor. */
 
80  8569 toggle public XWikiDocumentArchive()
81    {
82    }
83   
84    // helper methods
85    /**
86    * @param cur - current version
87    * @param isMinor - is modification is minor
88    * @return next version
89    */
 
90  11 toggle protected Version createNextVersion(Version cur, boolean isMinor)
91    {
92  11 Version result;
93  11 if (cur == null) {
94  4 result = new Version(1, 1);
95  7 } else if (!isMinor) {
96  7 result = cur.getBase(1).next().newBranch(1);
97    } else {
98  0 result = cur.next();
99    }
100  11 return result;
101    }
102   
103    /** @param node - node added to versionToNode and fullNodes */
 
104  24959 toggle protected void updateNode(XWikiRCSNodeInfo node)
105    {
106  24959 Version ver = node.getId().getVersion();
107  24959 this.versionToNode.put(ver, node);
108  24959 if (!node.isDiff()) {
109  14918 this.fullVersions.add(ver);
110    } else {
111  10041 this.fullVersions.remove(ver);
112    }
113    }
114   
115    /**
116    * Make a patch. It is store only modified nodes(latest). New nodes need be saved after.
117    *
118    * @param newnode - new node information
119    * @param doc - document for that patch created
120    * @param context - used for loading node contents and generating xml
121    * @return node content for newnode
122    * @throws XWikiException if exception while loading content
123    */
 
124  8084 toggle protected XWikiRCSNodeContent makePatch(XWikiRCSNodeInfo newnode, XWikiDocument doc,
125    XWikiContext context) throws XWikiException
126    {
127  8084 XWikiRCSNodeContent result = new XWikiRCSNodeContent();
128  8084 result.setPatch(new XWikiPatch().setFullVersion(doc, context));
129  8084 newnode.setContent(result);
130  8084 XWikiRCSNodeInfo latestNode = getLatestNode();
131  8084 if (latestNode != null) {
132  2192 int nodesCount = getNodes().size();
133  2192 int nodesPerFull =
134  2192 context.getWiki() == null ? 5 : Integer.parseInt(context.getWiki().getConfig()
135    .getProperty("xwiki.store.rcs.nodesPerFull", "5"));
136  2192 if (nodesPerFull <= 0 || (nodesCount % nodesPerFull) != 0) {
137  2092 XWikiRCSNodeContent latestContent = latestNode.getContent(context);
138  2092 latestContent.getPatch().setDiffVersion(latestContent.getPatch().getContent(),
139    doc, context);
140  2092 latestNode.setContent(latestContent);
141  2092 updateNode(latestNode);
142  2092 getUpdatedNodeContents().add(latestContent);
143    }
144    }
145  8084 return result;
146    }
147   
148    /** @return {@link XWikiDocument#getId()} - primary key */
 
149  16495 toggle public long getId()
150    {
151  16495 return this.id;
152    }
153   
154    /** @param id = {@link XWikiDocument#getId()} */
 
155  8416 toggle public void setId(long id)
156    {
157  8416 this.id = id;
158    }
159   
160    /** @return collection of XWikiRCSNodeInfo order by version desc */
 
161  10993 toggle public Collection<XWikiRCSNodeInfo> getNodes()
162    {
163  10993 return this.versionToNode.values();
164    }
165   
166    /**
167    * @return collection of XWikiRCSNodeInfo where vfrom &gt;= version &gt;= vto order by version desc
168    * @param vfrom - start version
169    * @param vto - end version
170    */
 
171  51 toggle public Collection<XWikiRCSNodeInfo> getNodes(Version vfrom, Version vto)
172    {
173  51 int[] ito = vto.getNumbers();
174  51 ito[1]--;
175  51 return this.versionToNode.subMap(vfrom, new Version(ito)).values();
176    }
177   
178    /** @param versions - collection of XWikiRCSNodeInfo */
 
179  8137 toggle public void setNodes(Collection<XWikiRCSNodeInfo> versions)
180    {
181  8137 resetArchive();
182  8137 for (XWikiRCSNodeInfo node : versions) {
183  11931 updateNode(node);
184    }
185  8137 if (getNodes().size() > 0) {
186    // ensure latest version is full
187  2496 getLatestNode().setDiff(false);
188  2496 updateNode(getLatestNode());
189    }
190    }
191   
192    /**
193    * @param context - used for load nodes content
194    * @return serialization of class used in {@link com.xpn.xwiki.plugin.packaging.PackagePlugin}.
195    * @throws XWikiException if any error
196    */
 
197  373 toggle public String getArchive(XWikiContext context) throws XWikiException
198    {
199  373 XWikiRCSArchive archive = new XWikiRCSArchive(getNodes(), context);
200  373 return archive.toString();
201    }
202   
203    /**
204    * Deserialize class. Used in {@link com.xpn.xwiki.plugin.packaging.PackagePlugin}.
205    *
206    * @param text - archive in JRCS format
207    * @throws XWikiException if parse error
208    */
 
209  274 toggle public void setArchive(String text) throws XWikiException
210    {
211  274 try {
212  274 XWikiRCSArchive archive = new XWikiRCSArchive(text);
213  274 resetArchive();
214  274 Collection nodes = archive.getNodes(getId());
215  626 for (Iterator it = nodes.iterator(); it.hasNext();) {
216  352 XWikiRCSNodeInfo nodeInfo = (XWikiRCSNodeInfo) it.next();
217  352 XWikiRCSNodeContent nodeContent = (XWikiRCSNodeContent) it.next();
218  352 updateNode(nodeInfo);
219  352 this.updatedNodeInfos.add(nodeInfo);
220  352 this.updatedNodeContents.add(nodeContent);
221    }
222    } catch (Exception e) {
223  0 Object[] args = { text, Long.valueOf(getId()) };
224  0 throw new XWikiException(XWikiException.MODULE_XWIKI_DIFF,
225    XWikiException.ERROR_XWIKI_DIFF_CONTENT_ERROR,
226    "Exception while constructing archive for JRCS string [{0}] for document [{1}]", e, args);
227    }
228    }
229   
230    /**
231    * Update history with new document version.
232    *
233    * @param doc - document for this version
234    * @param author - author of version
235    * @param date - date of version
236    * @param comment - version comment
237    * @param version - preferably document version in history
238    * @param context - used for loading nodes content
239    * @throws XWikiException in any error
240    */
 
241  8084 toggle public void updateArchive(XWikiDocument doc, String author, Date date, String comment,
242    Version version, XWikiContext context) throws XWikiException
243    {
244  8084 Version oldLatestVer = getLatestVersion();
245  8084 Version newVer = version;
246  8084 if (newVer == null || oldLatestVer != null && newVer.compareVersions(oldLatestVer) <= 0) {
247  11 newVer = createNextVersion(oldLatestVer, doc.isMinorEdit());
248    }
249  8084 XWikiRCSNodeInfo newNode = new XWikiRCSNodeInfo(new XWikiRCSNodeId(getId(), newVer));
250  8084 newNode.setAuthor(author);
251  8084 newNode.setComment(comment);
252  8084 newNode.setDate(date);
253  8084 XWikiRCSNodeContent newContent = makePatch(newNode, doc, context);
254   
255  8084 updateNode(newNode);
256  8084 this.updatedNodeInfos.add(newNode);
257  8084 this.updatedNodeContents.add(newContent);
258    }
259   
260    /**
261    * Remove document versions from vfrom to vto, inclusive.
262    *
263    * @param newerVersion - start version
264    * @param olderVersion - end version
265    * @param context - used for loading nodes content
266    * @throws XWikiException if any error
267    */
 
268  4 toggle public void removeVersions(Version newerVersion, Version olderVersion, XWikiContext context)
269    throws XWikiException
270    {
271  4 Version upperBound = newerVersion;
272  4 Version lowerBound = olderVersion;
273  4 if (upperBound.compareVersions(lowerBound) < 0) {
274  1 Version tmp = upperBound;
275  1 upperBound = lowerBound;
276  1 lowerBound = tmp;
277    }
278  4 Version firstVersionAfter = getNextVersion(upperBound);
279  4 Version firstVersionBefore = getPrevVersion(lowerBound);
280  4 if (firstVersionAfter == null && firstVersionBefore == null) {
281  0 resetArchive();
282  0 return;
283    }
284  4 if (firstVersionAfter == null) {
285    // Deleting the most recent version.
286    // Store full version in firstVersionBefore
287  3 String xmlBefore = getVersionXml(firstVersionBefore, context);
288  3 XWikiRCSNodeInfo niBefore = getNode(firstVersionBefore);
289  3 XWikiRCSNodeContent ncBefore = niBefore.getContent(context);
290  3 ncBefore.getPatch().setFullVersion(xmlBefore);
291  3 niBefore.setContent(ncBefore);
292  3 updateNode(niBefore);
293  3 getUpdatedNodeContents().add(ncBefore);
294  1 } else if (firstVersionBefore != null) {
295    // We're not deleting from the first version, so we must make a new diff jumping over
296    // the deleted versions.
297  1 String xmlAfter = getVersionXml(firstVersionAfter, context);
298  1 String xmlBefore = getVersionXml(firstVersionBefore, context);
299  1 XWikiRCSNodeInfo niBefore = getNode(firstVersionBefore);
300  1 XWikiRCSNodeContent ncBefore = niBefore.getContent(context);
301  1 ncBefore.getPatch().setDiffVersion(xmlBefore, xmlAfter, "");
302  1 niBefore.setContent(ncBefore);
303  1 updateNode(niBefore);
304  1 getUpdatedNodeContents().add(ncBefore);
305    }
306    // if (firstVersionBefore == null) => nothing else to do, except delete
307  9 for (Iterator<XWikiRCSNodeInfo> it = getNodes(upperBound, lowerBound).iterator(); it.hasNext();) {
308  5 XWikiRCSNodeInfo ni = it.next();
309  5 this.fullVersions.remove(ni.getId().getVersion());
310  5 this.deletedNodes.add(ni);
311  5 it.remove();
312    }
313    }
314   
315    /**
316    * @return selected version of document, null if version is not found.
317    * @param version - which version to load
318    * @param context - used for loading
319    * @throws XWikiException if any error
320    */
 
321  48 toggle public XWikiDocument loadDocument(Version version, XWikiContext context)
322    throws XWikiException
323    {
324  48 XWikiRCSNodeInfo nodeInfo = getNode(version);
325  48 if (nodeInfo == null) {
326  6 return null;
327    }
328  42 try {
329  42 String content = getVersionXml(version, context);
330  42 XWikiDocument doc = new XWikiDocument();
331  42 doc.fromXML(content);
332   
333  42 doc.setRCSVersion(version);
334  42 doc.setComment(nodeInfo.getComment());
335  42 doc.setAuthor(nodeInfo.getAuthor());
336  42 doc.setMinorEdit(nodeInfo.isMinorEdit());
337  42 doc.setMostRecent(version.equals(getLatestVersion()));
338   
339    // A document coming from the archive should never be considered new.
340  42 doc.setNew(false);
341   
342  42 return doc;
343    } catch (Exception e) {
344  0 Object[] args = { version.toString(), Long.valueOf(getId()) };
345  0 throw new XWikiException(XWikiException.MODULE_XWIKI_STORE,
346    XWikiException.ERROR_XWIKI_STORE_RCS_READING_REVISIONS,
347    "Exception while reading version [{0}] for document id [{1,number}]", e, args);
348    }
349    }
350   
351    /**
352    * Return the XML corresponding to a version. If the version node contains just a diff, then restore the complete
353    * XML by applying all patches from the nearest full version to the requested version.
354    *
355    * @param version The version to retrieve.
356    * @param context The {@link com.xpn.xwiki.XWikiContext context}.
357    * @return The XML corresponding to the version.
358    * @throws XWikiException If any exception occured.
359    */
 
360  47 toggle public String getVersionXml(Version version, XWikiContext context) throws XWikiException
361    {
362  47 Version nearestFullVersion = getNearestFullVersion(version);
363   
364  47 List<XWikiRCSNodeContent> lstContent = loadRCSNodeContents(nearestFullVersion, version, context);
365  47 List<String> origText = new ArrayList<String>();
366  47 for (XWikiRCSNodeContent nodeContent : lstContent) {
367  94 nodeContent.getPatch().patch(origText);
368    }
369   
370  47 return ToString.arrayToString(origText.toArray());
371    }
372   
373    /**
374    * @return {@link XWikiRCSNodeInfo} by version. null if none.
375    * @param version which version to get
376    */
 
377  13230 toggle public XWikiRCSNodeInfo getNode(Version version)
378    {
379  13230 return version == null ? null : (XWikiRCSNodeInfo) this.versionToNode.get(version);
380    }
381   
382    /** @return latest version in history for document. null if none. */
 
383  29288 toggle public Version getLatestVersion()
384    {
385  29288 return this.versionToNode.size() == 0 ? null : (Version) this.versionToNode.firstKey();
386    }
387   
388    /** @return latest node in history for document. null if none. */
 
389  13076 toggle public XWikiRCSNodeInfo getLatestNode()
390    {
391  13076 return getNode(getLatestVersion());
392    }
393   
394    /**
395    * @return next version in history. null if none
396    * @param ver - current version
397    */
 
398  24 toggle public Version getNextVersion(Version ver)
399    {
400    // headMap is exclusive
401  24 SortedMap<Version, XWikiRCSNodeInfo> headmap = this.versionToNode.headMap(ver);
402  24 return (headmap.size() == 0) ? null : headmap.lastKey();
403    }
404   
405    /**
406    * @return previous version in history. null if none
407    * @param ver - current version
408    */
 
409  36 toggle public Version getPrevVersion(Version ver)
410    {
411    // tailMap is inclusive
412  36 SortedMap<Version, XWikiRCSNodeInfo> tailmap = this.versionToNode.tailMap(ver);
413  36 if (tailmap.size() <= 1) {
414  28 return null;
415    }
416  8 Iterator<Version> it = tailmap.keySet().iterator();
417  8 it.next();
418  8 return it.next();
419    }
420   
421    /**
422    * @param ver - for what version find nearest
423    * @return nearest version which contain full information (not patch)
424    */
 
425  47 toggle public Version getNearestFullVersion(Version ver)
426    {
427  47 if (this.fullVersions.contains(ver)) {
428  11 return ver;
429    }
430  36 SortedSet<Version> headSet = this.fullVersions.headSet(ver);
431  36 return (headSet.size() == 0) ? null : headSet.last();
432    }
433   
434    /**
435    * @return List of {@link XWikiRCSNodeContent} where vfrom<=version<=vto order by version
436    * @param vfrom - start version
437    * @param vto - end version
438    * @param context - used everywhere
439    * @throws XWikiException if any error
440    */
 
441  47 toggle private List<XWikiRCSNodeContent> loadRCSNodeContents(Version vfrom, Version vto, XWikiContext context)
442    throws XWikiException
443    {
444  47 List<XWikiRCSNodeContent> result = new ArrayList<XWikiRCSNodeContent>();
445  47 for (XWikiRCSNodeInfo nodeInfo : getNodes(vfrom, vto)) {
446  94 XWikiRCSNodeContent nodeContent = nodeInfo.getContent(context);
447  94 result.add(nodeContent);
448    }
449  47 return result;
450    }
451   
452    /** reset history. history becomes empty. */
 
453  8551 toggle public void resetArchive()
454    {
455  8551 this.versionToNode.clear();
456  8551 this.fullVersions.clear();
457  8551 this.deletedNodes.addAll(this.updatedNodeInfos);
458  8551 this.updatedNodeInfos.clear();
459  8551 this.updatedNodeContents.clear();
460    }
461   
462    /** @return mutable Set of {@link XWikiRCSNodeInfo} which are need for delete */
 
463  16416 toggle public Set<XWikiRCSNodeInfo> getDeletedNodeInfo()
464    {
465  16416 return this.deletedNodes;
466    }
467   
468    /** @return mutable Set of {@link XWikiRCSNodeInfo} which are need for saveOrUpdate */
 
469  16281 toggle public Set<XWikiRCSNodeInfo> getUpdatedNodeInfos()
470    {
471  16281 return this.updatedNodeInfos;
472    }
473   
474    /** @return mutable Set of {@link XWikiRCSNodeContent} which are need for update */
 
475  18377 toggle public Set<XWikiRCSNodeContent> getUpdatedNodeContents()
476    {
477  18377 return this.updatedNodeContents;
478    }
479   
480    /**
481    * @return full copy of this archive with specified docId
482    * @param docId - new {@link #getId()}
483    * @param context - used for loading content
484    * @throws XWikiException if any error
485    */
 
486  82 toggle public XWikiDocumentArchive clone(long docId, XWikiContext context) throws XWikiException
487    {
488  82 XWikiDocumentArchive result = new XWikiDocumentArchive(docId);
489  82 result.setArchive(getArchive(context));
490  82 return result;
491    }
492    }