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

File TagPlugin.java

 

Coverage histogram

../../../../../img/srcFileCovDistChart0.png
83% of files have more coverage

Code metrics

32
121
24
1
585
266
46
0.38
5.04
24
1.92

Classes

Class Line # Actions
TagPlugin 51 121 0% 46 177
0.00%
 

Contributing tests

No tests hitting this source file were found.

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.plugin.tag;
21   
22    import java.util.ArrayList;
23    import java.util.Arrays;
24    import java.util.Collection;
25    import java.util.List;
26    import java.util.ListIterator;
27    import java.util.Map;
28    import java.util.regex.Pattern;
29   
30    import org.apache.commons.lang3.StringUtils;
31    import org.slf4j.Logger;
32    import org.slf4j.LoggerFactory;
33   
34    import com.xpn.xwiki.XWikiContext;
35    import com.xpn.xwiki.XWikiException;
36    import com.xpn.xwiki.api.Api;
37    import com.xpn.xwiki.doc.XWikiDocument;
38    import com.xpn.xwiki.objects.BaseObject;
39    import com.xpn.xwiki.objects.BaseProperty;
40    import com.xpn.xwiki.objects.DBStringListProperty;
41    import com.xpn.xwiki.objects.classes.BaseClass;
42    import com.xpn.xwiki.objects.classes.PropertyClass;
43    import com.xpn.xwiki.plugin.XWikiDefaultPlugin;
44    import com.xpn.xwiki.plugin.XWikiPluginInterface;
45   
46    /**
47    * TagPlugin is a plugin that allows to manipulate tags easily. It allows to get, rename and delete tags.
48    *
49    * @version $Id: 736a8e95b881b2d3334812e1552943aad80a6fca $
50    */
 
51    public class TagPlugin extends XWikiDefaultPlugin implements XWikiPluginInterface
52    {
53    /** Logging helper object. */
54    public static final Logger LOGGER = LoggerFactory.getLogger(TagPlugin.class);
55   
56    /**
57    * The identifier for this plugin; used for accessing the plugin from velocity, and as the action returning the
58    * extension content.
59    */
60    public static final String PLUGIN_NAME = "tag";
61   
62    /**
63    * XWiki class defining tags.
64    */
65    public static final String TAG_CLASS = "XWiki.TagClass";
66   
67    /**
68    * XWiki property of XWiki.TagClass storing tags.
69    */
70    public static final String TAG_PROPERTY = "tags";
71   
72    /**
73    * L10N key for the "tag added" document edit comment.
74    */
75    public static final String DOC_COMMENT_TAG_ADDED = "plugin.tag.editcomment.added";
76   
77    private static final Pattern LIKE_ESCAPE = Pattern.compile("[_%\\\\]");
78   
79    private static final String LIKE_REPLACEMENT = "\\\\$0";
80   
81    private static final String LIKE_APPEND = ".%";
82   
83    /**
84    * Tag plugin constructor.
85    *
86    * @param name The name of the plugin, which can be used for retrieving the plugin API from velocity. Unused.
87    * @param className The canonical classname of the plugin. Unused.
88    * @param context The current request context.
89    * @see com.xpn.xwiki.plugin.XWikiDefaultPlugin#XWikiDefaultPlugin(String,String,com.xpn.xwiki.XWikiContext)
90    */
 
91  0 toggle public TagPlugin(String name, String className, XWikiContext context)
92    {
93  0 super(PLUGIN_NAME, className, context);
94    }
95   
 
96  0 toggle @Override
97    public Api getPluginApi(XWikiPluginInterface plugin, XWikiContext context)
98    {
99  0 return new TagPluginApi((TagPlugin) plugin, context);
100    }
101   
102    /**
103    * Get tags of the given document.
104    *
105    * @param document document to search in.
106    * @return list of tags. The list is a snapshot of the current tags. Changes to this list won't affect the document,
107    * and changes to the document's tags won't be visible in the returned list.
108    */
 
109  0 toggle @SuppressWarnings("unchecked")
110    private List<String> getTagsFromDocument(XWikiDocument document)
111    {
112  0 try {
113  0 BaseProperty prop = (BaseProperty) document.getObject(TAG_CLASS).safeget(TAG_PROPERTY);
114  0 return new ArrayList<String>((List<String>) prop.getValue());
115    } catch (NullPointerException ex) {
116  0 return new ArrayList<String>();
117    }
118    }
119   
120    /**
121    * Set tags of the given document.
122    *
123    * @param document document to put the tags to.
124    * @param tags list of tags.
125    * @param context XWiki context.
126    */
 
127  0 toggle private void setDocumentTags(XWikiDocument document, List<String> tags, XWikiContext context)
128    {
129  0 BaseProperty prop = (BaseProperty) document.getObject(TAG_CLASS, true, context).safeget(TAG_PROPERTY);
130    // Properties aren't added to an object unless a value is specified either from the Web or from an XML.
131  0 if (prop == null) {
132  0 prop = createTagProperty(document.getObject(TAG_CLASS, true, context), context);
133    }
134  0 prop.setValue(tags);
135    }
136   
137    /**
138    * Create and add the main tag property to the provided tag object. The new property corresponds to the definition
139    * in the tag class, but in case of an error, the default type is a relational-stored list.
140    *
141    * @param tagObject the target tag object
142    * @param context the current request context
143    * @return the created property
144    * @see #TAG_PROPERTY
145    */
 
146  0 toggle private BaseProperty createTagProperty(BaseObject tagObject, XWikiContext context)
147    {
148  0 BaseProperty tagProperty;
149  0 try {
150  0 BaseClass tagClass = context.getWiki().getClass(TAG_CLASS, context);
151  0 PropertyClass tagPropertyDefinition = (PropertyClass) tagClass.getField(TAG_PROPERTY);
152  0 tagProperty = tagPropertyDefinition.newProperty();
153    } catch (XWikiException ex) {
154  0 LOGGER.warn("Failed to properly create tag property for the tag object, creating a default one");
155  0 tagProperty = new DBStringListProperty();
156    }
157  0 tagProperty.setName(TAG_PROPERTY);
158  0 tagProperty.setObject(tagObject);
159  0 tagObject.safeput(TAG_PROPERTY, tagProperty);
160  0 return tagProperty;
161    }
162   
163    /**
164    * Get all tags within the wiki.
165    *
166    * @param context XWiki context.
167    * @return list of tags (alphabetical order).
168    * @throws XWikiException if search query fails (possible failures: DB access problems, etc).
169    */
 
170  0 toggle public List<String> getAllTags(XWikiContext context) throws XWikiException
171    {
172  0 return TagQueryUtils.getAllTags(context);
173    }
174   
175    /**
176    * Get cardinality map of tags within the wiki.
177    *
178    * @param context XWiki context.
179    * @return map of tags (alphabetical order) with their occurences counts.
180    * @throws XWikiException if search query fails (possible failures: DB access problems, etc).
181    */
 
182  0 toggle public Map<String, Integer> getTagCount(XWikiContext context) throws XWikiException
183    {
184  0 return this.getTagCountForQuery(null, null, context);
185    }
186   
187    /**
188    * Get cardinality map of tags for a specific wiki space (including sub spaces).
189    *
190    * @param spaceReference the local reference of the space to get tags from. If blank, return tags for the whole
191    * wiki.
192    * @param context XWiki context.
193    * @return map of tags (alphabetical order) with their occurrences counts.
194    * @throws XWikiException if search query fails (possible failures: DB access problems, etc).
195    * @since 1.2
196    */
 
197  0 toggle public Map<String, Integer> getTagCount(String spaceReference, XWikiContext context) throws XWikiException
198    {
199  0 if (!StringUtils.isBlank(spaceReference)) {
200  0 StringBuilder where = new StringBuilder();
201  0 where.append('(');
202  0 where.append("doc.space = ?");
203  0 where.append(" OR ");
204  0 where.append("doc.space LIKE ?");
205  0 where.append(')');
206   
207    // Make sure to escape the LIKE syntax
208  0 String escapedSpaceReference = LIKE_ESCAPE.matcher(spaceReference).replaceAll(LIKE_REPLACEMENT);
209   
210  0 return getTagCountForQuery("", where.toString(),
211    Arrays.asList(spaceReference, escapedSpaceReference + LIKE_APPEND), context);
212    }
213   
214  0 return getTagCount(context);
215    }
216   
217   
218    /**
219    * Get cardinality map of tags for a list of wiki spaces (including sub spaces).
220    * For example "'Main','Sandbox'" for all tags in the "Main" and "Sandbox" spaces,
221    * or "'Apo''stroph'" for all tags in the space "Apo'stroph".
222    *
223    * @param spaces the list of space to get tags in, as a comma separated, quoted space references strings.
224    * @param context XWiki context.
225    * @return map of tags with their occurences counts
226    * @throws XWikiException if search query fails (possible failures: space list parse error, DB problems, etc).
227    * @since 8.2M1
228    */
 
229  0 toggle public Map<String, Integer> getTagCountForSpaces(String spaces, XWikiContext context) throws XWikiException
230    {
231  0 List<String> spaceRefList = TagParamUtils.spacesParameterToList(spaces);
232   
233  0 List<Object> queryParameter = new ArrayList<>();
234  0 StringBuilder where = new StringBuilder();
235  0 boolean first = true;
236  0 for (String spaceReference : spaceRefList) {
237  0 if (first) {
238  0 where.append("(doc.space = ? ");
239  0 first = false;
240    } else {
241  0 where.append(" OR doc.space = ? ");
242    }
243  0 queryParameter.add(spaceReference);
244  0 where.append("OR doc.space LIKE ?");
245  0 String escapedSpaceReference = LIKE_ESCAPE.matcher(spaceReference).replaceAll(LIKE_REPLACEMENT);
246  0 queryParameter.add(escapedSpaceReference + LIKE_APPEND);
247    }
248    // if first is true the "for" loop never ran, and spaces is empty
249    // so only close brace if first is false
250  0 if (!first) {
251  0 where.append(')');
252    }
253   
254  0 return getTagCountForQuery("", where.toString(), queryParameter, context);
255    }
256   
257    /**
258    * Get cardinality map of tags matching a hql query.
259    *
260    * @param fromHql the <code>from</code> fragment of the hql query
261    * @param whereHql the <code>where</code> fragment of the hql query
262    * @param context XWiki context.
263    * @return map of tags (alphabetical order) with their occurrences counts.
264    * @throws XWikiException if search query fails (possible failures: DB access problems, etc).
265    * @since 1.2
266    * @see TagPluginApi#getTagCountForQuery(String, String)
267    */
 
268  0 toggle public Map<String, Integer> getTagCountForQuery(String fromHql, String whereHql, XWikiContext context)
269    throws XWikiException
270    {
271  0 return getTagCountForQuery(fromHql, whereHql, null, context);
272    }
273   
274    /**
275    * Get cardinality map of tags matching a parameterized hql query.
276    *
277    * @param fromHql the <code>from</code> fragment of the hql query
278    * @param whereHql the <code>where</code> fragment of the hql query
279    * @param parameterValues list of parameter values for the query
280    * @param context XWiki context.
281    * @return map of tags (alphabetical order) with their occurrences counts.
282    * @throws XWikiException if search query fails (possible failures: DB access problems, etc).
283    * @since 1.18
284    * @see TagPluginApi#getTagCountForQuery(String, String, java.util.List)
285    */
 
286  0 toggle public Map<String, Integer> getTagCountForQuery(String fromHql, String whereHql, List<?> parameterValues,
287    XWikiContext context) throws XWikiException
288    {
289  0 return TagQueryUtils.getTagCountForQuery(fromHql, whereHql, parameterValues, context);
290    }
291   
292    /**
293    * Get non-hidden documents with the given tags.
294    *
295    * @param tag a list of tags to match.
296    * @param context XWiki context.
297    * @return list of docNames.
298    * @throws XWikiException if search query fails (possible failures: DB access problems, etc).
299    */
 
300  0 toggle public List<String> getDocumentsWithTag(String tag, XWikiContext context) throws XWikiException
301    {
302  0 return TagQueryUtils.getDocumentsWithTag(tag, context);
303    }
304   
305    /**
306    * Get documents with the given tags.
307    *
308    * @param tag a list of tags to match.
309    * @param includeHiddenDocuments if true then also include hidden documents
310    * @param context XWiki context.
311    * @return list of docNames.
312    * @throws XWikiException if search query fails (possible failures: DB access problems, etc).
313    * @since 6.2M1
314    */
 
315  0 toggle public List<String> getDocumentsWithTag(String tag, boolean includeHiddenDocuments, XWikiContext context)
316    throws XWikiException
317    {
318  0 return TagQueryUtils.getDocumentsWithTag(tag, includeHiddenDocuments, context);
319    }
320   
321    /**
322    * Get tags from a document.
323    *
324    * @param documentName name of the document.
325    * @param context XWiki context.
326    * @return list of tags.
327    * @throws XWikiException if document read fails (possible failures: insufficient rights, DB access problems, etc).
328    */
 
329  0 toggle public List<String> getTagsFromDocument(String documentName, XWikiContext context) throws XWikiException
330    {
331  0 return getTagsFromDocument(context.getWiki().getDocument(documentName, context));
332    }
333   
334    /**
335    * Get tags from a document.
336    *
337    * @param document the document.
338    * @param context XWiki context.
339    * @return list of tags.
340    * @throws XWikiException if document read fails (possible failures: insufficient rights, DB access problems, etc).
341    */
 
342  0 toggle public List<String> getTagsFromDocument(XWikiDocument document, XWikiContext context) throws XWikiException
343    {
344  0 return getTagsFromDocument(document);
345    }
346   
347    /**
348    * Add a tag to a document. The document is saved (minor edit) after this operation.
349    *
350    * @param tag tag to set.
351    * @param documentName name of the document.
352    * @param context XWiki context.
353    * @return the {@link TagOperationResult result} of the operation
354    * @throws XWikiException if document save fails (possible failures: insufficient rights, DB access problems, etc).
355    */
 
356  0 toggle public TagOperationResult addTagToDocument(String tag, String documentName, XWikiContext context)
357    throws XWikiException
358    {
359  0 return addTagToDocument(tag, context.getWiki().getDocument(documentName, context), context);
360    }
361   
362    /**
363    * Add a tag to a document. The document is saved (minor edit) after this operation.
364    *
365    * @param tag tag to set.
366    * @param document the document.
367    * @param context XWiki context.
368    * @return the {@link TagOperationResult result} of the operation
369    * @throws XWikiException if document save fails (possible failures: insufficient rights, DB access problems, etc).
370    */
 
371  0 toggle public TagOperationResult addTagToDocument(String tag, XWikiDocument document, XWikiContext context)
372    throws XWikiException
373    {
374  0 List<String> tags = getTagsFromDocument(document);
375  0 if (!StringUtils.isBlank(tag) && !tags.contains(tag)) {
376  0 tags.add(tag);
377  0 setDocumentTags(document, tags, context);
378   
379  0 String comment = localizePlainOrKey(DOC_COMMENT_TAG_ADDED, tag);
380   
381    // Since we're changing the document we need to set the new author
382  0 document.setAuthorReference(context.getUserReference());
383   
384  0 context.getWiki().saveDocument(document, comment, true, context);
385   
386  0 return TagOperationResult.OK;
387    }
388  0 return TagOperationResult.NO_EFFECT;
389    }
390   
391    /**
392    * Add a list of tags to a document. The document is saved (minor edit) after this operation.
393    *
394    * @param tags the comma separated list of tags to set; whitespace around the tags is stripped
395    * @param documentName the name of the target document
396    * @param context the current request context.
397    * @return the {@link TagOperationResult result} of the operation. {@link TagOperationResult#NO_EFFECT} is returned
398    * only if all the tags were already set on the document, {@link TagOperationResult#OK} is returned even if
399    * only some of the tags are new.
400    * @throws XWikiException if document save fails (possible failures: insufficient rights, DB access problems, etc).
401    */
 
402  0 toggle public TagOperationResult addTagsToDocument(String tags, String documentName, XWikiContext context)
403    throws XWikiException
404    {
405  0 return addTagsToDocument(tags, context.getWiki().getDocument(documentName, context), context);
406    }
407   
408    /**
409    * Add a list of tags to a document. The document is saved (minor edit) after this operation.
410    *
411    * @param tags the comma separated list of tags to set; whitespace around the tags is stripped
412    * @param document the target document
413    * @param context the current request context
414    * @return the {@link TagOperationResult result} of the operation. {@link TagOperationResult#NO_EFFECT} is returned
415    * only if all the tags were already set on the document, {@link TagOperationResult#OK} is returned even if
416    * only some of the tags are new.
417    * @throws XWikiException if document save fails (possible failures: insufficient rights, DB access problems, etc).
418    */
 
419  0 toggle public TagOperationResult addTagsToDocument(String tags, XWikiDocument document, XWikiContext context)
420    throws XWikiException
421    {
422  0 List<String> documentTags = getTagsFromDocument(document);
423  0 String[] newTags = tags.trim().split("\\s*+,\\s*+");
424  0 boolean added = false;
425   
426  0 for (String tag : newTags) {
427  0 if (!StringUtils.isBlank(tag) && !containsIgnoreCase(documentTags, tag)) {
428  0 documentTags.add(tag);
429  0 added = true;
430    }
431    }
432   
433  0 if (added) {
434  0 setDocumentTags(document, documentTags, context);
435  0 String comment = localizePlainOrKey(DOC_COMMENT_TAG_ADDED, tags);
436   
437    // Since we're changing the document we need to set the new author
438  0 document.setAuthorReference(context.getUserReference());
439   
440  0 context.getWiki().saveDocument(document, comment, true, context);
441   
442  0 return TagOperationResult.OK;
443    }
444   
445  0 return TagOperationResult.NO_EFFECT;
446    }
447   
448    /**
449    * @param collection a collection of strings
450    * @param item a string
451    * @return {@code true} if there is an item in the given collection that equals ignoring case the given string
452    */
 
453  0 toggle private boolean containsIgnoreCase(Collection<String> collection, String item)
454    {
455  0 for (String existingItem : collection) {
456  0 if (existingItem.equalsIgnoreCase(item)) {
457  0 return true;
458    }
459    }
460  0 return false;
461    }
462   
463    /**
464    * Remove a tag from a document. The document is saved (minor edit) after this operation.
465    *
466    * @param tag tag to remove.
467    * @param documentName name of the document.
468    * @param context XWiki context.
469    * @return the {@link TagOperationResult result} of the operation
470    * @throws XWikiException if document save fails for some reason (Insufficient rights, DB access, etc).
471    */
 
472  0 toggle public TagOperationResult removeTagFromDocument(String tag, String documentName, XWikiContext context)
473    throws XWikiException
474    {
475  0 return removeTagFromDocument(tag, context.getWiki().getDocument(documentName, context), context);
476    }
477   
478    /**
479    * Remove a tag from a document. The document is saved (minor edit) after this operation.
480    *
481    * @param tag tag to remove.
482    * @param document the document.
483    * @param context XWiki context.
484    * @return the {@link TagOperationResult result} of the operation
485    * @throws XWikiException if document save fails for some reason (Insufficient rights, DB access, etc).
486    */
 
487  0 toggle public TagOperationResult removeTagFromDocument(String tag, XWikiDocument document, XWikiContext context)
488    throws XWikiException
489    {
490  0 List<String> tags = getTagsFromDocument(document);
491  0 boolean needsUpdate = false;
492   
493  0 ListIterator<String> it = tags.listIterator();
494  0 while (it.hasNext()) {
495  0 if (tag.equalsIgnoreCase(it.next())) {
496  0 needsUpdate = true;
497  0 it.remove();
498    }
499    }
500   
501  0 if (needsUpdate) {
502  0 setDocumentTags(document, tags, context);
503  0 String comment = localizePlainOrKey("plugin.tag.editcomment.removed", tag);
504   
505    // Since we're changing the document we need to set the new author
506  0 document.setAuthorReference(context.getUserReference());
507   
508  0 context.getWiki().saveDocument(document, comment, true, context);
509   
510  0 return TagOperationResult.OK;
511    } else {
512    // Document doesn't contain this tag.
513  0 return TagOperationResult.NO_EFFECT;
514    }
515    }
516   
517    /**
518    * Rename a tag.
519    *
520    * @param tag tag to rename.
521    * @param newTag new tag.
522    * @param context XWiki context.
523    * @return the {@link TagOperationResult result} of the operation
524    * @throws XWikiException if document save fails for some reason (Insufficient rights, DB access, etc).
525    */
 
526  0 toggle protected TagOperationResult renameTag(String tag, String newTag, XWikiContext context) throws XWikiException
527    {
528    // Since we're renaming a tag, we want to rename it even if the document is hidden. A hidden document is still
529    // accessible to users, it's just not visible for simple users; it doesn't change permissions.
530  0 List<String> docNamesToProcess = getDocumentsWithTag(tag, true, context);
531  0 if (StringUtils.equals(tag, newTag) || docNamesToProcess.size() == 0 || StringUtils.isBlank(newTag)) {
532  0 return TagOperationResult.NO_EFFECT;
533    }
534   
535  0 String comment = localizePlainOrKey("plugin.tag.editcomment.renamed", tag, newTag);
536   
537  0 for (String docName : docNamesToProcess) {
538  0 XWikiDocument doc = context.getWiki().getDocument(docName, context);
539  0 List<String> tags = getTagsFromDocument(doc);
540   
541  0 if (tags.contains(newTag)) {
542    // The new tag might already be present in the document, in this case we just need to remove the old one
543  0 removeTagFromDocument(tag, doc.getFullName(), context);
544    } else {
545  0 for (int i = 0; i < tags.size(); i++) {
546  0 if (tags.get(i).equalsIgnoreCase(tag)) {
547  0 tags.set(i, newTag);
548    }
549    }
550  0 setDocumentTags(doc, tags, context);
551   
552    // Since we're changing the document we need to set the new author
553  0 doc.setAuthorReference(context.getUserReference());
554   
555  0 context.getWiki().saveDocument(doc, comment, true, context);
556    }
557    }
558   
559  0 return TagOperationResult.OK;
560    }
561   
562    /**
563    * Delete a tag.
564    *
565    * @param tag tag to delete.
566    * @param context XWiki context.
567    * @return the {@link TagOperationResult result} of the operation
568    * @throws XWikiException if document save fails for some reason (Insufficient rights, DB access, etc).
569    */
 
570  0 toggle protected TagOperationResult deleteTag(String tag, XWikiContext context) throws XWikiException
571    {
572    // Since we're deleting a tag, we want to delete it even if the document is hidden. A hidden document is still
573    // accessible to users, it's just not visible for simple users; it doesn't change permissions.
574  0 List<String> docsToProcess = getDocumentsWithTag(tag, true, context);
575   
576  0 if (docsToProcess.size() == 0) {
577  0 return TagOperationResult.NO_EFFECT;
578    }
579  0 for (String docName : docsToProcess) {
580  0 removeTagFromDocument(tag, docName, context);
581    }
582   
583  0 return TagOperationResult.OK;
584    }
585    }