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

File AbstractDocumentSkinExtensionPlugin.java

 

Coverage histogram

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

Code metrics

26
91
20
1
469
241
37
0.41
4.55
20
1.85

Classes

Class Line # Actions
AbstractDocumentSkinExtensionPlugin 66 91 0% 37 26
0.81021981%
 

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.skinx;
21   
22    import java.util.ArrayList;
23    import java.util.Collections;
24    import java.util.HashMap;
25    import java.util.HashSet;
26    import java.util.List;
27    import java.util.Locale;
28    import java.util.Map;
29    import java.util.Set;
30   
31    import org.apache.commons.lang3.StringUtils;
32    import org.slf4j.Logger;
33    import org.slf4j.LoggerFactory;
34    import org.xwiki.bridge.event.DocumentCreatedEvent;
35    import org.xwiki.bridge.event.DocumentDeletedEvent;
36    import org.xwiki.bridge.event.DocumentUpdatedEvent;
37    import org.xwiki.bridge.event.WikiDeletedEvent;
38    import org.xwiki.model.EntityType;
39    import org.xwiki.model.reference.DocumentReference;
40    import org.xwiki.model.reference.EntityReferenceResolver;
41    import org.xwiki.model.reference.EntityReferenceSerializer;
42    import org.xwiki.observation.EventListener;
43    import org.xwiki.observation.ObservationManager;
44    import org.xwiki.observation.event.Event;
45    import org.xwiki.security.authorization.AuthorizationManager;
46    import org.xwiki.security.authorization.ContextualAuthorizationManager;
47    import org.xwiki.security.authorization.Right;
48   
49    import com.xpn.xwiki.XWikiContext;
50    import com.xpn.xwiki.XWikiException;
51    import com.xpn.xwiki.doc.XWikiDocument;
52    import com.xpn.xwiki.objects.BaseObject;
53    import com.xpn.xwiki.objects.classes.BaseClass;
54    import com.xpn.xwiki.web.Utils;
55   
56    /**
57    * Abstract SX plugin for wiki-document-based extensions (Extensions written as object of a XWiki Extension class).
58    * Provides a generic method to initialize the XWiki class upon plugin initialization if needed. Provide a notification
59    * mechanism for extensions marked as "use-always".
60    *
61    * @version $Id: fe38e7c2bb8f22e475a36b1002c823cee4f3e07f $
62    * @since 1.4
63    * @see JsSkinExtensionPlugin
64    * @see CssSkinExtensionPlugin
65    */
 
66    public abstract class AbstractDocumentSkinExtensionPlugin extends AbstractSkinExtensionPlugin implements EventListener
67    {
68    /**
69    * Log helper for logging messages in this class.
70    */
71    protected static final Logger LOGGER = LoggerFactory.getLogger(AbstractDocumentSkinExtensionPlugin.class);
72   
73    /**
74    * The name of the field that indicates whether an extension should always be used, or only when explicitly pulled.
75    */
76    private static final String USE_FIELDNAME = "use";
77   
78    /**
79    * A Map with wiki/database name as keys and sets of extensions to use always for this wiki as values.
80    */
81    private Map<String, Set<DocumentReference>> alwaysUsedExtensions;
82   
83    /**
84    * Used to match events on "use" property.
85    */
86    private final List<Event> events = new ArrayList<Event>(3);
87   
88    /**
89    * XWiki plugin constructor.
90    *
91    * @param name The name of the plugin, which can be used for retrieving the plugin API from velocity. Unused.
92    * @param className The canonical classname of the plugin. Unused.
93    * @param context The current request context.
94    * @see com.xpn.xwiki.plugin.XWikiDefaultPlugin#XWikiDefaultPlugin(String,String,com.xpn.xwiki.XWikiContext)
95    */
 
96  64 toggle public AbstractDocumentSkinExtensionPlugin(String name, String className, XWikiContext context)
97    {
98  64 super(name, className, context);
99   
100  64 this.events.add(new DocumentCreatedEvent());
101  64 this.events.add(new DocumentDeletedEvent());
102  64 this.events.add(new DocumentUpdatedEvent());
103   
104  64 this.events.add(new WikiDeletedEvent());
105    }
106   
 
107  64 toggle @Override
108    public List<Event> getEvents()
109    {
110  64 return this.events;
111    }
112   
113    /**
114    * The name of the XClass which holds extensions of this type.
115    *
116    * @return A <code>String</code> representation of the XClass name, in the <code>Space.Document</code> format.
117    */
118    protected abstract String getExtensionClassName();
119   
120    /**
121    * A user-friendly name for this type of resource, used in the auto-generated class document.
122    *
123    * @return The user-friendly name for this type of resource.
124    */
125    protected abstract String getExtensionName();
126   
127    /**
128    * {@inheritDoc}
129    * <p>
130    * Create/update the XClass corresponding to this kind of extension, and register the listeners that update the list
131    * of always used extensions.
132    * </p>
133    *
134    * @see com.xpn.xwiki.plugin.XWikiDefaultPlugin#init(com.xpn.xwiki.XWikiContext)
135    */
 
136  64 toggle @Override
137    public void init(XWikiContext context)
138    {
139  64 super.init(context);
140   
141  64 this.alwaysUsedExtensions = new HashMap<String, Set<DocumentReference>>();
142  64 getExtensionClass(context);
143   
144  64 Utils.getComponent(ObservationManager.class).addListener(this);
145    }
146   
147    /**
148    * {@inheritDoc}
149    * <p>
150    * Create/update the XClass corresponding to this kind of extension in this virtual wiki.
151    * </p>
152    *
153    * @see com.xpn.xwiki.plugin.XWikiDefaultPlugin#virtualInit(com.xpn.xwiki.XWikiContext)
154    */
 
155  8 toggle @Override
156    public void virtualInit(XWikiContext context)
157    {
158  8 super.virtualInit(context);
159   
160  8 getExtensionClass(context);
161    }
162   
163    /**
164    * {@inheritDoc}
165    * <p>
166    * For this kind of resources, an XObject property (<tt>use</tt>) with the value <tt>always</tt> indicates always
167    * used extensions. The list of extensions for each wiki is lazily placed in a cache: if the extension set for the
168    * context wiki is null, then they will be looked up in the database and added to it. The cache is invalidated using
169    * the notification mechanism.
170    * </p>
171    *
172    * @see AbstractSkinExtensionPlugin#getAlwaysUsedExtensions(XWikiContext)
173    */
 
174  13785 toggle @Override
175    public Set<String> getAlwaysUsedExtensions(XWikiContext context)
176    {
177  13779 EntityReferenceSerializer<String> serializer = Utils.getComponent(EntityReferenceSerializer.TYPE_STRING);
178  13785 Set<DocumentReference> references = getAlwaysUsedExtensions();
179  13778 Set<String> names = new HashSet<String>(references.size());
180  13783 for (DocumentReference reference : references) {
181  15314 names.add(serializer.serialize(reference));
182    }
183  13778 return names;
184    }
185   
186    /**
187    * Returns the list of always used extensions of this type as a set of document references. For this kind of
188    * resources, an XObject property (<tt>use</tt>) with the value <tt>always</tt> indicates always used extensions.
189    * The list of extensions for each wiki is lazily placed in a cache: if the extension set for the context wiki is
190    * null, then they will be looked up in the database and added to it. The cache is invalidated using the
191    * notification mechanism. Note that this method is called for each request, as the list might change in time, and
192    * it can be different for each wiki in a farm.
193    *
194    * @return a set of document references that should be pulled in the current response
195    */
 
196  13793 toggle public Set<DocumentReference> getAlwaysUsedExtensions()
197    {
198  13793 XWikiContext context = Utils.getContext();
199    // Retrieve the current wiki name from the XWiki context
200  13798 String currentWiki = StringUtils.defaultIfEmpty(context.getWikiId(), context.getMainXWiki());
201    // If we already have extensions defined for this wiki, we return them
202  13798 if (this.alwaysUsedExtensions.get(currentWiki) != null) {
203  13712 return this.alwaysUsedExtensions.get(currentWiki);
204    } else {
205    // Otherwise, we look them up in the database.
206  76 Set<DocumentReference> extensions = new HashSet<DocumentReference>();
207  76 String query =
208    ", BaseObject as obj, StringProperty as use where obj.className='" + getExtensionClassName() + "'"
209    + " and obj.name=doc.fullName and use.id.id=obj.id and use.id.name='use' and use.value='always'";
210  76 try {
211  76 for (DocumentReference extension : context.getWiki().getStore()
212    .searchDocumentReferences(query, context)) {
213  10 try {
214  10 XWikiDocument doc = context.getWiki().getDocument(extension, context);
215    // Only add the extension as being "always used" if the page holding it has been saved with
216    // programming rights.
217  10 if (Utils.getComponent(AuthorizationManager.class).hasAccess(Right.PROGRAM,
218    doc.getContentAuthorReference(), doc.getDocumentReference())) {
219  10 extensions.add(extension);
220    }
221    } catch (XWikiException e1) {
222  0 LOGGER.error("Error while adding skin extension [{}] as always used. It will be ignored.",
223    extension, e1);
224    }
225    }
226  76 this.alwaysUsedExtensions.put(currentWiki, extensions);
227  76 return extensions;
228    } catch (XWikiException e) {
229  0 LOGGER.error("Error while retrieving always used JS extensions", e);
230  0 return Collections.emptySet();
231    }
232    }
233    }
234   
 
235  13783 toggle @Override
236    public boolean hasPageExtensions(XWikiContext context)
237    {
238  13780 XWikiDocument doc = context.getDoc();
239  13783 List<BaseObject> objects = doc.getObjects(getExtensionClassName());
240  13781 if (objects != null) {
241  124 for (BaseObject obj : objects) {
242  124 if (obj == null) {
243  0 continue;
244    }
245  124 if (obj.getStringValue(USE_FIELDNAME).equals("currentPage")) {
246  56 return true;
247    }
248    }
249    }
250  13729 return false;
251    }
252   
 
253  3856 toggle @Override
254    public void use(String resource, XWikiContext context)
255    {
256  3856 String canonicalResource = getCanonicalDocumentName(resource);
257  3856 LOGGER.debug("Using [{}] as [{}] extension", canonicalResource, this.getName());
258  3856 getPulledResources(context).add(canonicalResource);
259    // In case a previous call added some parameters, remove them, since the last call for a resource always
260    // discards previous ones.
261  3856 getParametersMap(context).remove(canonicalResource);
262    }
263   
 
264  172 toggle @Override
265    public void use(String resource, Map<String, Object> parameters, XWikiContext context)
266    {
267  172 String canonicalResource = getCanonicalDocumentName(resource);
268  172 getPulledResources(context).add(canonicalResource);
269  172 getParametersMap(context).put(canonicalResource, parameters);
270    }
271   
272    /**
273    * {@inheritDoc}
274    * <p>
275    * We must override this method since the plugin manager only calls it for classes that provide their own
276    * implementation, and not an inherited one.
277    * </p>
278    *
279    * @see AbstractSkinExtensionPlugin#endParsing(String, XWikiContext)
280    */
 
281  13784 toggle @Override
282    public String endParsing(String content, XWikiContext context)
283    {
284  13785 return super.endParsing(content, context);
285    }
286   
287    /**
288    * Creates or updates the XClass used for this type of extension. Usually called on {@link #init(XWikiContext)} and
289    * {@link #virtualInit(XWikiContext)}.
290    *
291    * @param context The current request context, which gives access to the wiki.
292    * @return The XClass for this extension.
293    */
 
294  72 toggle public BaseClass getExtensionClass(XWikiContext context)
295    {
296  72 try {
297  72 XWikiDocument doc = context.getWiki().getDocument(getExtensionClassName(), context);
298   
299  72 return doc.getXClass();
300    } catch (Exception ex) {
301  0 LOGGER.error("Cannot get skin extension class [{}]", getExtensionClassName(), ex);
302    }
303   
304  0 return null;
305    }
306   
307    /**
308    * {@inheritDoc}
309    * <p>
310    * Make sure to keep the {@link #alwaysUsedExtensions} map consistent when the database changes.
311    *
312    * @see org.xwiki.observation.EventListener#onEvent(org.xwiki.observation.event.Event, java.lang.Object,
313    * java.lang.Object)
314    */
 
315  2116 toggle @Override
316    public void onEvent(Event event, Object source, Object data)
317    {
318  2116 if (event instanceof WikiDeletedEvent) {
319  6 this.alwaysUsedExtensions.remove(((WikiDeletedEvent) event).getWikiId());
320    } else {
321  2110 onDocumentEvent((XWikiDocument) source, (XWikiContext) data);
322    }
323    }
324   
325    /**
326    * A document related event has been received.
327    *
328    * @param document the modified document
329    * @param context the XWiki context
330    */
 
331  2110 toggle private void onDocumentEvent(XWikiDocument document, XWikiContext context)
332    {
333  2110 boolean remove = false;
334  2110 if (document.getObject(getExtensionClassName()) != null) {
335    // new or already existing object
336  12 if (document.getObject(getExtensionClassName(), USE_FIELDNAME, "always", false) != null) {
337  0 if (Utils.getComponent(AuthorizationManager.class).hasAccess(Right.PROGRAM,
338    document.getContentAuthorReference(), document.getDocumentReference())) {
339  0 getAlwaysUsedExtensions().add(document.getDocumentReference());
340   
341  0 return;
342    } else {
343    // in case the extension lost its programming rights upon this save.
344  0 remove = true;
345    }
346    } else {
347    // remove if exists but use onDemand
348  12 remove = true;
349    }
350  2098 } else if (document.getOriginalDocument().getObject(getExtensionClassName()) != null) {
351    // object removed
352  0 remove = true;
353    }
354   
355  2110 if (remove) {
356  12 getAlwaysUsedExtensions().remove(document.getDocumentReference());
357    }
358    }
359   
360    /**
361    * Get the canonical serialization of a document name, in the {@code wiki:Space.Document} format.
362    *
363    * @param documentName the original document name to fix
364    * @return fixed document name
365    */
 
366  4028 toggle private String getCanonicalDocumentName(String documentName)
367    {
368  4028 @SuppressWarnings("unchecked")
369    EntityReferenceResolver<String> resolver = Utils.getComponent(EntityReferenceResolver.TYPE_STRING, "current");
370  4028 @SuppressWarnings("unchecked")
371    EntityReferenceSerializer<String> serializer = Utils.getComponent(EntityReferenceSerializer.TYPE_STRING);
372  4028 return serializer.serialize(resolver.resolve(documentName, EntityType.DOCUMENT));
373    }
374   
375    /**
376    * @param documentName the Skin Extension's document name
377    * @param context the XWiki Context
378    * @return true if the specified document is accessible (i.e. has view rights) by the current user; false otherwise
379    */
 
380  0 toggle protected boolean isAccessible(String documentName, XWikiContext context)
381    {
382  0 return isAccessible(getCurrentDocumentReferenceResolver().resolve(documentName), context);
383    }
384   
385    /**
386    * @param documentReference the Skin Extension's document reference
387    * @param context the XWiki Context
388    * @return true if the specified document is accessible (i.e. has view rights) by the current user; false otherwise
389    * @since 7.4.1
390    */
 
391  17295 toggle protected boolean isAccessible(DocumentReference documentReference, XWikiContext context)
392    {
393  17295 if (!Utils.getComponent(ContextualAuthorizationManager.class).hasAccess(Right.VIEW, documentReference)) {
394  0 LOGGER.debug("[{}] The current user [{}] does not have 'view' rights on the Skin Extension document [{}]",
395    getName(), context.getUserReference(), documentReference);
396   
397  0 return false;
398    }
399   
400  17295 return true;
401    }
402   
403    /**
404    * @param documentReference the Skin Extension's document reference
405    * @param context the XWiki Context
406    * @return the version of the document
407    */
 
408  17295 toggle private String getDocumentVersion(DocumentReference documentReference, XWikiContext context)
409    {
410  17295 try {
411  17295 return context.getWiki().getDocument(documentReference, context).getVersion();
412    } catch (XWikiException e) {
413  0 LOGGER.error("Failed to load document [{}].", documentReference, e);
414    }
415  0 return "";
416    }
417   
418    /**
419    * Return the query string part with the version of the document, to add to the URL of a resource. The objective is
420    * to generate an URL specific to this version to avoid browsers using an outdated version from their cache.
421    *
422    * @param documentReference the Skin Extension's document reference
423    * @param context the XWiki Context
424    * @return the query string part handling the version of the document
425    */
 
426  17295 toggle private String getDocumentVersionQueryString(DocumentReference documentReference, XWikiContext context)
427    {
428  17295 return "docVersion=" + sanitize(getDocumentVersion(documentReference, context));
429    }
430   
431    /**
432    * Return the query string part with the language of the document (if any).
433    *
434    * @param context the XWiki Context
435    * @return the query string handling the language of the document
436    */
 
437  17295 toggle private String getLanguageQueryString(XWikiContext context)
438    {
439  17295 Locale locale = context.getLocale();
440  17295 if (locale != null) {
441  17295 return "language=" + sanitize(locale.toString());
442    }
443  0 return "";
444    }
445   
446    /**
447    * Return the URL to a document skin extension.
448    *
449    * @param documentReference the Skin Extension's document reference
450    * @param documentName the Skin Extension's document name
451    * @param pluginName the name of the plugin
452    * @param context the XWiki Context
453    * @return the URL to the document skin extension.
454    *
455    * @since 7.4.1
456    */
 
457  17295 toggle protected String getDocumentSkinExtensionURL(DocumentReference documentReference, String documentName,
458    String pluginName, XWikiContext context)
459    {
460  17295 String queryString = String.format("%s&amp;%s%s",
461    getLanguageQueryString(context),
462    getDocumentVersionQueryString(documentReference, context),
463    parametersAsQueryString(documentName, context));
464   
465  17295 return context.getWiki().getURL(documentReference, pluginName, queryString, "", context);
466    }
467   
468   
469    }