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

File XWikiRCSArchive.java

 

Coverage histogram

../../../../../img/srcFileCovDistChart9.png
41% of files have more coverage

Code metrics

30
94
15
2
333
206
35
0.37
6.27
7.5
2.33

Classes

Class Line # Actions
XWikiRCSArchive 56 51 0% 12 1
0.9855072598.6%
XWikiRCSArchive.XWikiJRCSNode 114 43 0% 23 18
0.7428571674.3%
 

Contributing tests

This file is covered by 7 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.rcs;
21   
22    import java.io.StringReader;
23    import java.util.ArrayList;
24    import java.util.BitSet;
25    import java.util.Collection;
26    import java.util.Date;
27    import java.util.Iterator;
28    import java.util.List;
29   
30    import org.apache.commons.codec.DecoderException;
31    import org.apache.commons.codec.net.URLCodec;
32    import org.apache.commons.lang3.StringUtils;
33    import org.slf4j.Logger;
34    import org.slf4j.LoggerFactory;
35    import org.suigeneris.jrcs.diff.PatchFailedException;
36    import org.suigeneris.jrcs.rcs.Archive;
37    import org.suigeneris.jrcs.rcs.InvalidFileFormatException;
38    import org.suigeneris.jrcs.rcs.InvalidTrunkVersionNumberException;
39    import org.suigeneris.jrcs.rcs.Version;
40    import org.suigeneris.jrcs.rcs.impl.Node;
41    import org.suigeneris.jrcs.rcs.impl.NodeNotFoundException;
42    import org.suigeneris.jrcs.rcs.impl.TrunkNode;
43    import org.suigeneris.jrcs.rcs.parse.ParseException;
44    import org.suigeneris.jrcs.util.ToString;
45   
46    import com.xpn.xwiki.XWikiContext;
47    import com.xpn.xwiki.XWikiException;
48    import com.xpn.xwiki.doc.XWikiDocument;
49   
50    /**
51    * Class for String [de]serialization for {@link com.xpn.xwiki.doc.XWikiDocumentArchive}.
52    *
53    * @version $Id: 5069347a725bd0534b735fa5fb9d64f616bbf2f3 $
54    * @since 1.2M1
55    */
 
56    public class XWikiRCSArchive extends Archive
57    {
58    /** logger. */
59    private static final Logger LOGGER = LoggerFactory.getLogger(XWikiRCSArchive.class);
60   
61    /**
62    * Used to serialize {@link com.xpn.xwiki.doc.XWikiDocumentArchive}.
63    *
64    * @param nodeInfos - collection of {@link XWikiRCSNodeInfo} in any order
65    * @param context - for loading nodes content
66    * @throws XWikiException if can't load nodes content
67    */
 
68  373 toggle public XWikiRCSArchive(Collection<XWikiRCSNodeInfo> nodeInfos, XWikiContext context) throws XWikiException
69    {
70  373 super(new Object[0], "");
71  373 this.nodes.clear();
72  373 this.head = null;
73  373 if (nodeInfos.size() > 0) {
74  362 for (XWikiRCSNodeInfo nodeInfo : nodeInfos) {
75  696 XWikiJRCSNode node = new XWikiJRCSNode(nodeInfo.getId().getVersion(), null);
76  696 node.setAuthor(nodeInfo.getAuthor());
77  696 node.setDate(nodeInfo.getDate());
78  696 node.setLog(nodeInfo.getComment());
79  696 XWikiRCSNodeContent content = nodeInfo.getContent(context);
80    // Ensure we never set the text to NULL since this can cause errors on some DB such as Oracle.
81  696 node.setText(StringUtils.defaultString(content.getPatch().getContent()));
82  696 node.setDiff(nodeInfo.isDiff());
83  696 this.nodes.put(node.getVersion(), node);
84    }
85  362 XWikiJRCSNode last = null;
86  1058 for (Iterator it = this.nodes.keySet().iterator(); it.hasNext();) {
87  696 Version ver = (Version) it.next();
88  696 XWikiJRCSNode node = (XWikiJRCSNode) this.nodes.get(ver);
89  696 if (last != null) {
90  334 last.setRCSNext(node);
91    }
92  696 last = node;
93  696 if (this.head == null) {
94  362 this.head = node;
95    }
96    }
97    }
98    }
99   
100    /**
101    * Used to deserialize {@link com.xpn.xwiki.doc.XWikiDocumentArchive}.
102    *
103    * @param archiveText - archive text in JRCS format
104    * @throws ParseException if syntax errors
105    */
 
106  274 toggle public XWikiRCSArchive(String archiveText) throws ParseException
107    {
108  274 super("", new StringReader(archiveText));
109    }
110   
111    /**
112    * Helper class for convert from {@link XWikiRCSNodeInfo} to JRCS {@link Node}.
113    */
 
114    private static class XWikiJRCSNode extends TrunkNode
115    {
116    /** bug if author=="". see http://www.suigeneris.org/issues/browse/JRCS-24 */
117    public static String sauthorIfEmpty = "_";
118   
119    /** mark that node contains full text, not diff. */
120    public static String sfullVersion = "full";
121   
122    /** mark that node contains diff. */
123    public static String sdiffVersion = "diff";
124   
125    /**
126    * @param vernum - version of node
127    * @param next - next node (with smaller version) in history
128    * @throws InvalidTrunkVersionNumberException if version is invalid
129    */
 
130  1048 toggle public XWikiJRCSNode(Version vernum, TrunkNode next)
131    throws InvalidTrunkVersionNumberException
132    {
133  1048 super(vernum, next);
134    }
135   
136    /** @param other - create class from copying this node */
 
137  352 toggle public XWikiJRCSNode(Node other)
138    {
139  352 this(other.version, null);
140  352 this.setDate(other.getDate());
141  352 this.author = other.getAuthor(); // setAuthor is encoding
142  352 this.setState(other.getState());
143  352 this.setLog(other.getLog());
144  352 this.setLocker(other.getLocker());
145  352 this.setText(other.getText());
146    }
147   
148    /**
149    * @param date - date of modification.
150    * @see Node#setDate(int[])
151    */
 
152  1048 toggle public void setDate(Date date)
153    {
154  1048 this.date = date;
155    }
156   
157    /** bitset of chars allowed in author field */
158    static BitSet safeAuthorChars = new BitSet();
 
159  17 toggle static {
160  17 safeAuthorChars.set('-');
161  459 for (char c = 'A', c1 = 'a'; c <= 'Z'; c++, c1++) {
162  442 safeAuthorChars.set(c);
163  442 safeAuthorChars.set(c1);
164    }
165    }
166   
167    /** @param user - user of modification */
 
168  696 toggle @Override
169    public void setAuthor(String user)
170    {
171    // empty author is error in jrcs
172  696 if (user == null || "".equals(user)) {
173  0 super.setAuthor(sauthorIfEmpty);
174    } else {
175  696 byte[] enc = URLCodec.encodeUrl(safeAuthorChars, user.getBytes());
176  696 String senc = new String(enc).replace('%', '_');
177  696 super.setAuthor(senc);
178    }
179    }
180   
181    /**
182    * @return user of modification can't override getAuthor, so getAuthor1
183    * @see Node#getAuthor()
184    */
 
185  351 toggle public String getAuthor1()
186    {
187  351 String result = super.getAuthor();
188  351 if (sauthorIfEmpty.equals(result)) {
189  0 return "";
190    } else {
191  351 result = result.replace('_', '%');
192  351 try {
193  351 byte[] dec = URLCodec.decodeUrl(result.getBytes());
194  351 result = new String(dec);
195    } catch (DecoderException e) {
196    // Probably the archive was created before introducing this encoding (1.2M1/M2).
197  0 result = super.getAuthor();
198  0 if (!result.matches("^(\\w|\\d|\\.)++$")) {
199    // It's safer to use an empty author than to use an invalid value.
200  0 result = "";
201    }
202    }
203    }
204  351 return result;
205    }
206   
207    /** @return is this node store diff or full version */
 
208  704 toggle public boolean isDiff()
209    {
210  704 boolean isdiff = !sfullVersion.equals(getState());
211  704 if (getTextString() != null && isdiff != !getTextString().startsWith("<")) {
212  2 LOGGER.warn("isDiff: Archive is inconsistent. Text and diff field are contradicting. version="
213    + getVersion());
214  2 isdiff = !isdiff;
215    }
216  704 return isdiff;
217    }
218   
219    /** @param isdiff - true if node stores a diff, false - if full version */
 
220  696 toggle public void setDiff(boolean isdiff)
221    {
222  696 if (getTextString() != null && isdiff != !getTextString().startsWith("<")) {
223  0 LOGGER.warn("setDiff: Archive is inconsistent. Text and diff field are contradicting. version="
224    + getVersion());
225  0 isdiff = !isdiff;
226    }
227  696 setState(isdiff ? sdiffVersion : sfullVersion);
228    }
229   
230    /** @return is this revision has old format. (xwiki-core<1.2, without author,comment,state fields) */
 
231  352 toggle public boolean hasOldFormat()
232    {
233  352 return !sfullVersion.equals(getState()) && !sdiffVersion.equals(getState());
234    }
235   
236    /**
237    * @return text of modification.
238    * @see Node#getText()
239    */
 
240  3152 toggle public String getTextString()
241    {
242  3152 return mergedText()[0].toString();
243    }
244   
 
245  0 toggle @Override
246    public void patch(List original, boolean annotate) throws InvalidFileFormatException, PatchFailedException
247    {
248  0 if (isDiff()) {
249  0 super.patch(original, annotate);
250    } else {
251    // there is full version, so simply copy. @see TrunkNode#patch0(..)
252    // impossible because org.suigeneris.jrcs.rcs.impl.Line is final with default constructor.
253  0 throw new IllegalArgumentException();
254    /*
255    * original.clear(); Object[] lines = getText(); for (int it = 0; it < lines.length; it++) {
256    * original.add(new Line(this, lines[it])); }
257    */
258    }
259    }
260    }
261   
262    /**
263    * @return Collection of pairs [{@link XWikiRCSNodeInfo}, {@link XWikiRCSNodeContent}]
264    * @param docId - docId which will be wrote in {@link XWikiRCSNodeId#setDocId(long)}
265    * @throws PatchFailedException
266    * @throws InvalidFileFormatException
267    * @throws NodeNotFoundException
268    */
 
269  274 toggle public Collection getNodes(long docId) throws NodeNotFoundException, InvalidFileFormatException,
270    PatchFailedException
271    {
272  274 Collection result = new ArrayList(this.nodes.values().size());
273  626 for (Iterator it = this.nodes.values().iterator(); it.hasNext();) {
274  352 XWikiJRCSNode node = new XWikiJRCSNode((Node) it.next());
275  352 XWikiRCSNodeInfo nodeInfo = new XWikiRCSNodeInfo();
276  352 nodeInfo.setId(new XWikiRCSNodeId(docId, node.getVersion()));
277  352 nodeInfo.setDiff(node.isDiff());
278   
279  352 if (!node.hasOldFormat()) {
280  351 nodeInfo.setAuthor(node.getAuthor1());
281  351 nodeInfo.setComment(node.getLog());
282  351 nodeInfo.setDate(node.getDate());
283    } else {
284    // If the archive node is old so there is no author, comment and date fields so we set them using the
285    // ones from a XWikiDocment object that we construct using the archive content.
286  1 try {
287  1 String xml = getRevisionAsString(node.getVersion());
288  1 XWikiDocument doc = new XWikiDocument();
289  1 doc.fromXML(xml);
290    // set this fields from old document
291  1 nodeInfo.setAuthor(doc.getAuthor());
292  1 nodeInfo.setComment(doc.getComment());
293  1 nodeInfo.setDate(doc.getDate());
294    } catch (Exception e) {
295    // 3 potential known errors:
296    // 1) Revision 1.1 doesn't exist. Some time in the past there was a bug in XWiki where version
297    // were starting at 1.2. When this happens the returned xml has a value of "\n".
298    // 2) A Class property with an invalid XML name was created.
299    // See https://jira.xwiki.org/browse/XWIKI-1855
300    // 3) Cannot get the revision as a string from a node version. Not sure why this
301    // is happening though... See https://jira.xwiki.org/browse/XWIKI-2076
302  0 LOGGER.warn("Error in revision [" + node.getVersion().toString() + "]: [" + e.getMessage()
303    + "]. Ignoring non-fatal error, the Author, Comment and Date are not set.");
304    }
305    }
306   
307  352 XWikiRCSNodeContent content = new XWikiRCSNodeContent(nodeInfo.getId());
308  352 content.setPatch(new XWikiPatch(node.getTextString(), node.isDiff()));
309  352 nodeInfo.setContent(content);
310  352 result.add(nodeInfo);
311  352 result.add(content);
312    }
313    // Ensure that the latest revision is set set to have the full content and not a diff.
314  274 if (result.size() > 0) {
315  264 ((XWikiRCSNodeInfo) ((ArrayList) result).get(0)).setDiff(false);
316  264 ((XWikiRCSNodeContent) ((ArrayList) result).get(1)).getPatch().setDiff(false);
317    }
318  274 return result;
319    }
320   
321    /**
322    * @return The text of the revision if found.
323    * @param version - the version number.
324    * @throws NodeNotFoundException if the revision could not be found.
325    * @throws InvalidFileFormatException if any of the deltas cannot be parsed.
326    * @throws PatchFailedException if any of the deltas could not be applied
327    */
 
328  1 toggle public String getRevisionAsString(Version version) throws NodeNotFoundException,
329    InvalidFileFormatException, PatchFailedException
330    {
331  1 return ToString.arrayToString(super.getRevision(version), RCS_NEWLINE);
332    }
333    }