1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package com.xpn.xwiki.doc

File XWikiDocumentArchive.java

 

Coverage histogram

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

Code metrics

42
151
28
1
488
299
55
0.36
5.39
28
1.96

Classes

Class Line # Actions
XWikiDocumentArchive 49 151 0% 55 12
0.9457013694.6%
 

Contributing tests

This file is covered by 9 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: 45e5d5f091d276b1ea97899a1b54872ba3a2da41 $
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  4101 toggle public XWikiDocumentArchive(long id)
74    {
75  4101 this();
76  4101 setId(id);
77    }
78   
79    /** default constructor. */
 
80  4105 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  10601 toggle protected void updateNode(XWikiRCSNodeInfo node)
105    {
106  10601 Version ver = node.getId().getVersion();
107  10601 this.versionToNode.put(ver, node);
108  10601 if (!node.isDiff()) {
109  7762 this.fullVersions.add(ver);
110    } else {
111  2839 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  5752 toggle protected XWikiRCSNodeContent makePatch(XWikiRCSNodeInfo newnode, XWikiDocument doc,
125    XWikiContext context) throws XWikiException
126    {
127  5752 XWikiRCSNodeContent result = new XWikiRCSNodeContent();
128  5752 result.setPatch(new XWikiPatch().setFullVersion(doc, context));
129  5752 newnode.setContent(result);
130  5752 XWikiRCSNodeInfo latestNode = getLatestNode();
131  5752 if (latestNode != null) {
132  408 int nodesCount = getNodes().size();
133  408 int nodesPerFull =
134  408 context.getWiki() == null ? 5 : Integer.parseInt(context.getWiki().getConfig()
135    .getProperty("xwiki.store.rcs.nodesPerFull", "5"));
136  408 if (nodesPerFull <= 0 || (nodesCount % nodesPerFull) != 0) {
137  379 XWikiRCSNodeContent latestContent = latestNode.getContent(context);
138  379 latestContent.getPatch().setDiffVersion(latestContent.getPatch().getContent(),
139    doc, context);
140  379 latestNode.setContent(latestContent);
141  379 updateNode(latestNode);
142  379 getUpdatedNodeContents().add(latestContent);
143    }
144    }
145  5752 return result;
146    }
147   
148    /** @return {@link XWikiDocument#getId()} - primary key */
 
149  9848 toggle public long getId()
150    {
151  9848 return this.id;
152    }
153   
154    /** @param id = {@link XWikiDocument#getId()} */
 
155  4101 toggle public void setId(long id)
156    {
157  4101 this.id = id;
158    }
159   
160    /** @return collection of XWikiRCSNodeInfo order by version desc */
 
161  6783 toggle public Collection<XWikiRCSNodeInfo> getNodes()
162    {
163  6783 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  40 toggle public Collection<XWikiRCSNodeInfo> getNodes(Version vfrom, Version vto)
172    {
173  40 int[] ito = vto.getNumbers();
174  40 ito[1]--;
175  40 return this.versionToNode.subMap(vfrom, new Version(ito)).values();
176    }
177   
178    /** @param versions - collection of XWikiRCSNodeInfo */
 
179  3962 toggle public void setNodes(Collection<XWikiRCSNodeInfo> versions)
180    {
181  3962 resetArchive();
182  3962 for (XWikiRCSNodeInfo node : versions) {
183  3623 updateNode(node);
184    }
185  3962 if (getNodes().size() > 0) {
186    // ensure latest version is full
187  694 getLatestNode().setDiff(false);
188  694 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  279 toggle public String getArchive(XWikiContext context) throws XWikiException
198    {
199  279 XWikiRCSArchive archive = new XWikiRCSArchive(getNodes(), context);
200  279 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  134 toggle public void setArchive(String text) throws XWikiException
210    {
211  134 try {
212  134 XWikiRCSArchive archive = new XWikiRCSArchive(text);
213  134 resetArchive();
214  134 Collection nodes = archive.getNodes(getId());
215  284 for (Iterator it = nodes.iterator(); it.hasNext();) {
216  150 XWikiRCSNodeInfo nodeInfo = (XWikiRCSNodeInfo) it.next();
217  150 XWikiRCSNodeContent nodeContent = (XWikiRCSNodeContent) it.next();
218  150 updateNode(nodeInfo);
219  150 this.updatedNodeInfos.add(nodeInfo);
220  150 this.updatedNodeContents.add(nodeContent);
221    }
222    } catch (Exception e) {
223  0 Object[] args = { text, new Long(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  5752 toggle public void updateArchive(XWikiDocument doc, String author, Date date, String comment,
242    Version version, XWikiContext context) throws XWikiException
243    {
244  5752 Version oldLatestVer = getLatestVersion();
245  5752 Version newVer = version;
246  5752 if (newVer == null || oldLatestVer != null && newVer.compareVersions(oldLatestVer) <= 0) {
247  11 newVer = createNextVersion(oldLatestVer, doc.isMinorEdit());
248    }
249  5752 XWikiRCSNodeInfo newNode = new XWikiRCSNodeInfo(new XWikiRCSNodeId(getId(), newVer));
250  5752 newNode.setAuthor(author);
251  5752 newNode.setComment(comment);
252  5752 newNode.setDate(date);
253  5752 XWikiRCSNodeContent newContent = makePatch(newNode, doc, context);
254   
255  5752 updateNode(newNode);
256  5752 this.updatedNodeInfos.add(newNode);
257  5752 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  3 toggle public void removeVersions(Version newerVersion, Version olderVersion, XWikiContext context)
269    throws XWikiException
270    {
271  3 Version upperBound = newerVersion;
272  3 Version lowerBound = olderVersion;
273  3 if (upperBound.compareVersions(lowerBound) < 0) {
274  1 Version tmp = upperBound;
275  1 upperBound = lowerBound;
276  1 lowerBound = tmp;
277    }
278  3 Version firstVersionAfter = getNextVersion(upperBound);
279  3 Version firstVersionBefore = getPrevVersion(lowerBound);
280  3 if (firstVersionAfter == null && firstVersionBefore == null) {
281  0 resetArchive();
282  0 return;
283    }
284  3 if (firstVersionAfter == null) {
285    // Deleting the most recent version.
286    // Store full version in firstVersionBefore
287  2 String xmlBefore = getVersionXml(firstVersionBefore, context);
288  2 XWikiRCSNodeInfo niBefore = getNode(firstVersionBefore);
289  2 XWikiRCSNodeContent ncBefore = niBefore.getContent(context);
290  2 ncBefore.getPatch().setFullVersion(xmlBefore);
291  2 niBefore.setContent(ncBefore);
292  2 updateNode(niBefore);
293  2 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  7 for (Iterator<XWikiRCSNodeInfo> it = getNodes(upperBound, lowerBound).iterator(); it.hasNext();) {
308  4 XWikiRCSNodeInfo ni = it.next();
309  4 this.fullVersions.remove(ni.getId().getVersion());
310  4 this.deletedNodes.add(ni);
311  4 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  35 toggle public XWikiDocument loadDocument(Version version, XWikiContext context)
322    throws XWikiException
323    {
324  35 XWikiRCSNodeInfo nodeInfo = getNode(version);
325  35 if (nodeInfo == null) {
326  2 return null;
327    }
328  33 try {
329  33 String content = getVersionXml(version, context);
330  33 XWikiDocument doc = new XWikiDocument();
331  33 doc.fromXML(content);
332   
333  33 doc.setRCSVersion(version);
334  33 doc.setComment(nodeInfo.getComment());
335  33 doc.setAuthor(nodeInfo.getAuthor());
336  33 doc.setMinorEdit(nodeInfo.isMinorEdit());
337  33 doc.setMostRecent(version.equals(getLatestVersion()));
338  33 return doc;
339    } catch (Exception e) {
340  0 Object[] args = { version.toString(), new Long(getId()) };
341  0 throw new XWikiException(XWikiException.MODULE_XWIKI_STORE,
342    XWikiException.ERROR_XWIKI_STORE_RCS_READING_REVISIONS,
343    "Exception while reading version [{0}] for document id [{1,number}]", e, args);
344    }
345    }
346   
347    /**
348    * Return the XML corresponding to a version. If the version node contains just a diff, then restore the complete
349    * XML by applying all patches from the nearest full version to the requested version.
350    *
351    * @param version The version to retrieve.
352    * @param context The {@link com.xpn.xwiki.XWikiContext context}.
353    * @return The XML corresponding to the version.
354    * @throws XWikiException If any exception occured.
355    */
 
356  37 toggle public String getVersionXml(Version version, XWikiContext context) throws XWikiException
357    {
358  37 Version nearestFullVersion = getNearestFullVersion(version);
359   
360  37 List<XWikiRCSNodeContent> lstContent = loadRCSNodeContents(nearestFullVersion, version, context);
361  37 List<String> origText = new ArrayList<String>();
362  37 for (XWikiRCSNodeContent nodeContent : lstContent) {
363  74 nodeContent.getPatch().patch(origText);
364    }
365   
366  37 return ToString.arrayToString(origText.toArray());
367    }
368   
369    /**
370    * @return {@link XWikiRCSNodeInfo} by version. null if none.
371    * @param version which version to get
372    */
 
373  7234 toggle public XWikiRCSNodeInfo getNode(Version version)
374    {
375  7234 return version == null ? null : (XWikiRCSNodeInfo) this.versionToNode.get(version);
376    }
377   
378    /** @return latest version in history for document. null if none. */
 
379  18673 toggle public Version getLatestVersion()
380    {
381  18673 return this.versionToNode.size() == 0 ? null : (Version) this.versionToNode.firstKey();
382    }
383   
384    /** @return latest node in history for document. null if none. */
 
385  7140 toggle public XWikiRCSNodeInfo getLatestNode()
386    {
387  7140 return getNode(getLatestVersion());
388    }
389   
390    /**
391    * @return next version in history. null if none
392    * @param ver - current version
393    */
 
394  3 toggle public Version getNextVersion(Version ver)
395    {
396    // headMap is exclusive
397  3 SortedMap<Version, XWikiRCSNodeInfo> headmap = this.versionToNode.headMap(ver);
398  3 return (headmap.size() == 0) ? null : headmap.lastKey();
399    }
400   
401    /**
402    * @return previous version in history. null if none
403    * @param ver - current version
404    */
 
405  10 toggle public Version getPrevVersion(Version ver)
406    {
407    // tailMap is inclusive
408  10 SortedMap<Version, XWikiRCSNodeInfo> tailmap = this.versionToNode.tailMap(ver);
409  10 if (tailmap.size() <= 1) {
410  3 return null;
411    }
412  7 Iterator<Version> it = tailmap.keySet().iterator();
413  7 it.next();
414  7 return it.next();
415    }
416   
417    /**
418    * @param ver - for what version find nearest
419    * @return nearest version which contain full information (not patch)
420    */
 
421  37 toggle public Version getNearestFullVersion(Version ver)
422    {
423  37 if (this.fullVersions.contains(ver)) {
424  10 return ver;
425    }
426  27 SortedSet<Version> headSet = this.fullVersions.headSet(ver);
427  27 return (headSet.size() == 0) ? null : headSet.last();
428    }
429   
430    /**
431    * @return List of {@link XWikiRCSNodeContent} where vfrom<=version<=vto order by version
432    * @param vfrom - start version
433    * @param vto - end version
434    * @param context - used everywhere
435    * @throws XWikiException if any error
436    */
 
437  37 toggle private List<XWikiRCSNodeContent> loadRCSNodeContents(Version vfrom, Version vto, XWikiContext context)
438    throws XWikiException
439    {
440  37 List<XWikiRCSNodeContent> result = new ArrayList<XWikiRCSNodeContent>();
441  37 for (XWikiRCSNodeInfo nodeInfo : getNodes(vfrom, vto)) {
442  74 XWikiRCSNodeContent nodeContent = nodeInfo.getContent(context);
443  74 result.add(nodeContent);
444    }
445  37 return result;
446    }
447   
448    /** reset history. history becomes empty. */
 
449  6168 toggle public void resetArchive()
450    {
451  6168 this.versionToNode.clear();
452  6168 this.fullVersions.clear();
453  6168 this.deletedNodes.addAll(this.updatedNodeInfos);
454  6168 this.updatedNodeInfos.clear();
455  6168 this.updatedNodeContents.clear();
456    }
457   
458    /** @return mutable Set of {@link XWikiRCSNodeInfo} which are need for delete */
 
459  13714 toggle public Set<XWikiRCSNodeInfo> getDeletedNodeInfo()
460    {
461  13714 return this.deletedNodes;
462    }
463   
464    /** @return mutable Set of {@link XWikiRCSNodeInfo} which are need for saveOrUpdate */
 
465  11647 toggle public Set<XWikiRCSNodeInfo> getUpdatedNodeInfos()
466    {
467  11647 return this.updatedNodeInfos;
468    }
469   
470    /** @return mutable Set of {@link XWikiRCSNodeContent} which are need for update */
 
471  12029 toggle public Set<XWikiRCSNodeContent> getUpdatedNodeContents()
472    {
473  12029 return this.updatedNodeContents;
474    }
475   
476    /**
477    * @return full copy of this archive with specified docId
478    * @param docId - new {@link #getId()}
479    * @param context - used for loading content
480    * @throws XWikiException if any error
481    */
 
482  122 toggle public XWikiDocumentArchive clone(long docId, XWikiContext context) throws XWikiException
483    {
484  122 XWikiDocumentArchive result = new XWikiDocumentArchive(docId);
485  122 result.setArchive(getArchive(context));
486  122 return result;
487    }
488    }