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

File AbstractSkinExtensionPlugin.java

 

Coverage histogram

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

Code metrics

16
59
17
1
395
162
26
0.44
3.47
17
1.53

Classes

Class Line # Actions
AbstractSkinExtensionPlugin 65 59 0% 26 7
0.9239130692.4%
 

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.io.UnsupportedEncodingException;
23    import java.net.URLEncoder;
24    import java.util.Collections;
25    import java.util.HashMap;
26    import java.util.LinkedHashSet;
27    import java.util.Map;
28    import java.util.Map.Entry;
29    import java.util.Set;
30   
31    import org.slf4j.Logger;
32    import org.slf4j.LoggerFactory;
33    import org.xwiki.model.reference.DocumentReferenceResolver;
34    import org.xwiki.model.reference.EntityReferenceSerializer;
35   
36    import com.xpn.xwiki.XWikiContext;
37    import com.xpn.xwiki.api.Api;
38    import com.xpn.xwiki.internal.cache.rendering.CachedItem;
39    import com.xpn.xwiki.internal.cache.rendering.CachedItem.UsedExtension;
40    import com.xpn.xwiki.internal.cache.rendering.RenderingCacheAware;
41    import com.xpn.xwiki.plugin.XWikiDefaultPlugin;
42    import com.xpn.xwiki.plugin.XWikiPluginInterface;
43    import com.xpn.xwiki.web.Utils;
44   
45    /**
46    * <p>
47    * Skin Extensions base plugin. It allows templates and document content to pull required clientside code in the
48    * generated XHTML (or whatever XML) content.
49    * </p>
50    * <p>
51    * The API provides a method {@link SkinExtensionPluginApi#use(String)}, which, when called, marks an extension as used
52    * in the current result. Later on, all the used extensions are inserted in the content, by replacing the first
53    * occurrence of the following string: <tt>&lt;!-- canonical.plugin.classname --&gt;</tt>, where the actual extension
54    * type classname is used. For example, JS extensions are inserted in place of
55    * <tt>&lt;!-- com.xpn.xwiki.plugin.skinx.JsSkinExtensionPlugin --&gt;</tt>.
56    * </p>
57    *
58    * @see SkinExtensionPluginApi
59    * @see JsSkinExtensionPlugin
60    * @see CssSkinExtensionPlugin
61    * @see LinkExtensionPlugin
62    * @version $Id: c3d056866ed3aaca50b193ce05d31445ae9872d5 $
63    */
64    @SuppressWarnings("deprecation")
 
65    public abstract class AbstractSkinExtensionPlugin extends XWikiDefaultPlugin implements RenderingCacheAware
66    {
67    /** Log object to log messages in this class. */
68    private static final Logger LOGGER = LoggerFactory.getLogger(AbstractSkinExtensionPlugin.class);
69   
70    /** The name of the context key for the list of pulled extensions. */
71    protected final String contextKey = this.getClass().getCanonicalName();
72   
73    /** The name of the context key for the additional parameters for pulled extensions. */
74    protected final String parametersContextKey = this.getClass().getCanonicalName() + "_parameters";
75   
76    /**
77    * @see #getDefaultEntityReferenceSerializer()
78    */
79    private EntityReferenceSerializer<String> defaultEntityReferenceSerializer;
80   
81    /**
82    * @see #getCurrentDocumentReferenceResolver()
83    */
84    private DocumentReferenceResolver<String> currentDocumentReferenceResolver;
85   
86    /**
87    * XWiki plugin constructor.
88    *
89    * @param name The name of the plugin, which can be used for retrieving the plugin API from velocity. Unused.
90    * @param className The canonical classname of the plugin. Unused.
91    * @param context The current request context.
92    * @see com.xpn.xwiki.plugin.XWikiDefaultPlugin#XWikiDefaultPlugin(String,String,com.xpn.xwiki.XWikiContext)
93    */
 
94  160 toggle public AbstractSkinExtensionPlugin(String name, String className, XWikiContext context)
95    {
96  160 super(name, className, context);
97    }
98   
99    /**
100    * Abstract method for obtaining a link that points to the actual pulled resource. Each type of resource has its own
101    * format for the link, for example Javascript uses <code>&lt;script src="/path/to/Document"&gt;</code>, while CSS
102    * uses <code>&lt;link rel="stylesheet" href="/path/to/Document"&gt;</code> (the actual syntax is longer, this is
103    * just a simplified example).
104    *
105    * @param resource the name of the wiki document holding the resource.
106    * @param context the current request context, needed to access the URLFactory.
107    * @return A <code>String</code> representation of the linking element that should be printed in the generated HTML.
108    */
109    public abstract String getLink(String resource, XWikiContext context);
110   
111    /**
112    * Returns the list of always used extensions of this type. Which resources are always used depends on the type of
113    * resource, for example document based StyleSheet extensions have a property in the object, <tt>use</tt>, which can
114    * have the value <tt>always</tt> to declare that an extension should always be used.
115    *
116    * @param context The current request context.
117    * @return A set of resource names that should be pulled in the current response. Note that this method is called
118    * for each request, as the list might change in time, and it can be different for each wiki in a farm.
119    */
120    public abstract Set<String> getAlwaysUsedExtensions(XWikiContext context);
121   
122    /**
123    * Determines if the requested document contains on page skin extension objects of this type. True if at least one
124    * of the extension objects has the <tt>currentPage</tt> value for the <tt>use</tt> property.
125    *
126    * @param context the current request context
127    * @return a boolean specifying if the current document contains on page skin extensions
128    */
129    public abstract boolean hasPageExtensions(XWikiContext context);
130   
 
131  5601 toggle @Override
132    public Api getPluginApi(XWikiPluginInterface plugin, XWikiContext context)
133    {
134  5601 return new SkinExtensionPluginApi((AbstractSkinExtensionPlugin) plugin, context);
135    }
136   
137    /**
138    * Mark a resource as used in the current result. A resource is registered only once per request, further calls will
139    * not result in additional links, even if it is pulled with different parameters.
140    *
141    * @param resource The name of the resource to pull.
142    * @param context The current request context.
143    * @see #use(String, Map, XWikiContext)
144    */
 
145  17540 toggle public void use(String resource, XWikiContext context)
146    {
147  17541 LOGGER.debug("Using [{}] as [{}] extension", resource, this.getName());
148  17541 getPulledResources(context).add(resource);
149    // In case a previous call added some parameters, remove them, since the last call for a resource always
150    // discards previous ones.
151  17541 getParametersMap(context).remove(resource);
152    }
153   
154    /**
155    * Mark a skin extension document as used in the current result, together with some parameters. How the parameters
156    * are used, depends on the type of resource being pulled. For example, JS and CSS extensions use the parameters in
157    * the resulting URL, while Link extensions use the parameters as attributes of the link tag. A resource is
158    * registered only once per request, further calls will not result in additional links, even if it is pulled with
159    * different parameters. If more than one calls per request are made, the parameters used are the ones from the last
160    * call (or none, if the last call did not specify any parameters).
161    *
162    * @param resource The name of the resource to pull.
163    * @param parameters The parameters for this resource.
164    * @param context The current request context.
165    * @see #use(String, XWikiContext)
166    */
 
167  17541 toggle public void use(String resource, Map<String, Object> parameters, XWikiContext context)
168    {
169  17541 use(resource, context);
170  17540 getParametersMap(context).put(resource, parameters);
171    }
172   
173    /**
174    * Get the list of pulled resources (of the plugin's type) for the current request. The returned list is always
175    * valid.
176    *
177    * @param context The current request context.
178    * @return A set of names that holds the resources pulled in the current request.
179    */
 
180  56024 toggle @SuppressWarnings("unchecked")
181    protected Set<String> getPulledResources(XWikiContext context)
182    {
183  56024 initializeRequestListIfNeeded(context);
184  56033 return (Set<String>) context.get(this.contextKey);
185    }
186   
187    /**
188    * Get the map of additional parameters for each pulled resource (of the plugin's type) for the current request. The
189    * returned map is always valid.
190    *
191    * @param context The current request context.
192    * @return A map of resource parameters, where the key is the resource's name, and the value is a map holding the
193    * actual parameters for a given resource. If a resource was pulled without additional parameters, then no
194    * corresponding entry is added in this map.
195    */
 
196  99684 toggle @SuppressWarnings("unchecked")
197    protected Map<String, Map<String, Object>> getParametersMap(XWikiContext context)
198    {
199  99685 initializeRequestListIfNeeded(context);
200  99687 return (Map<String, Map<String, Object>>) context.get(this.parametersContextKey);
201    }
202   
203    /**
204    * Initializes the list of pulled extensions corresponding to this request, if it wasn't already initialized. This
205    * method is not thread safe, since a context should not be shared among threads.
206    *
207    * @param context The current context where this list is stored.
208    */
 
209  155711 toggle protected void initializeRequestListIfNeeded(XWikiContext context)
210    {
211  155716 if (!context.containsKey(this.contextKey)) {
212  34468 context.put(this.contextKey, new LinkedHashSet<String>());
213    }
214  155712 if (!context.containsKey(this.parametersContextKey)) {
215  34467 context.put(this.parametersContextKey, new HashMap<String, Map<String, Object>>());
216    }
217    }
218   
219    /**
220    * Composes and returns the links to the resources pulled in the current request. This method is called at the end
221    * of each request, once for each type of resource (subclass), and the result is placed in the generated XHTML.
222    *
223    * @param context The current request context.
224    * @return a XHMTL fragment with all extensions imports statements for this request. This includes both extensions
225    * that are defined as being "used always" and "on demand" extensions explicitly requested for this page.
226    * Always used extensions are always, before on demand extensions, so that on demand extensions can override
227    * more general elements in the always used ones.
228    */
 
229  34462 toggle public String getImportString(XWikiContext context)
230    {
231  34464 StringBuilder result = new StringBuilder();
232    // Using LinkedHashSet to preserve the extensions order.
233  34458 Set<String> extensions = new LinkedHashSet<String>();
234    // First, we add to the import string the extensions that should always be used.
235    // TODO Global extensions should be able to select a set of actions for which they are enabled.
236  34458 extensions.addAll(getAlwaysUsedExtensions(context));
237   
238    // Then, we add On-Demand extensions for this request.
239  34459 extensions.addAll(getPulledResources(context));
240   
241    // Add On-Page extensions
242  34462 if (hasPageExtensions(context)) {
243    // Make sure to use a prefixed document full name for the current document as well, or else the "extensions"
244    // set will not detect if it was added before and it will be added twice.
245  56 EntityReferenceSerializer<String> serializer = getDefaultEntityReferenceSerializer();
246  56 String serializedCurrentDocumentName = serializer.serialize(context.getDoc().getDocumentReference());
247   
248    // Add it to the list.
249  56 extensions.add(serializedCurrentDocumentName);
250    }
251   
252  34458 for (String documentName : extensions) {
253  32568 result.append(getLink(documentName, context));
254    }
255  34459 return result.toString();
256    }
257   
258    /**
259    * Get the parameters for a pulled resource. Note that a valid map is always returned, even if no parameters were
260    * given when the resource was pulled.
261    *
262    * @param resource The resource for which to retrieve the parameters.
263    * @param context The current request context.
264    * @return The parameters for the resource, as a map where the keys are the parameter names, and the values are
265    * corresponding parameter value. If no parameters were given, an empty map is returned.
266    */
 
267  60573 toggle protected Map<String, Object> getParametersForResource(String resource, XWikiContext context)
268    {
269  60572 Map<String, Object> result = getParametersMap(context).get(resource);
270  60575 if (result == null) {
271  23209 result = Collections.emptyMap();
272    }
273  60575 return result;
274    }
275   
276    /**
277    * Get a parameter value for a pulled resource.
278    *
279    * @param parameterName the name of the parameter to retrieve
280    * @param resource the resource for which to retrieve the parameter
281    * @param context the current request context
282    * @return The parameter value for the resource. If this parameter was not given, {@code null} is returned.
283    */
 
284  24991 toggle protected Object getParameter(String parameterName, String resource, XWikiContext context)
285    {
286  24991 return getParametersForResource(resource, context).get(parameterName);
287    }
288   
289    /**
290    * This method converts the parameters for an extension to a query string that can be used with
291    * {@link com.xpn.xwiki.doc.XWikiDocument#getURL(String, String, String, XWikiContext) getURL()} and printed in the
292    * XHTML result. The parameters separator is the escaped &amp;amp;. The query string already starts with an
293    * &amp;amp; if at least one parameter exists.
294    *
295    * @param resource The pulled resource whose parameters should be converted.
296    * @param context The current request context.
297    * @return The constructed query string, or an empty string if there are no parameters.
298    */
 
299  29759 toggle protected String parametersAsQueryString(String resource, XWikiContext context)
300    {
301  29760 Map<String, Object> parameters = getParametersForResource(resource, context);
302  29760 StringBuilder query = new StringBuilder();
303  29759 for (Entry<String, Object> parameter : parameters.entrySet()) {
304    // Skip the parameter that forces the file extensions to be sent through the /skin/ action
305  19631 if ("forceSkinAction".equals(parameter.getKey())) {
306  12462 continue;
307    }
308  7169 query.append("&amp;");
309  7171 query.append(sanitize(parameter.getKey()));
310  7172 query.append("=");
311  7172 query.append(sanitize(parameter.getValue().toString()));
312    }
313    // If the main page is requested unminified, also send unminified extensions
314  29759 if ("false".equals(context.getRequest().getParameter("minify"))) {
315  0 query.append("&amp;minify=false");
316    }
317  29759 return query.toString();
318    }
319   
320    /**
321    * Prevent "HTML Injection" by making sure the rendered text does not escape the current element. This is achieved
322    * by URL-encoding the following characters: '"&lt;&gt;
323    *
324    * @param value The string to sanitize.
325    * @return The unchanged string, if it does not contain special characters, or the empty string.
326    */
 
327  48926 toggle protected String sanitize(String value)
328    {
329  48926 String result = value;
330  48927 try {
331  48926 result = URLEncoder.encode(value, "UTF-8");
332    } catch (UnsupportedEncodingException ex) {
333    // Should never happen since the UTF-8 encoding is always available in the platform,
334    // see http://java.sun.com/j2se/1.5.0/docs/api/java/nio/charset/Charset.html
335    }
336  48928 return result;
337    }
338   
339    /**
340    * {@inheritDoc}
341    * <p>
342    * At the end of the request, insert the links to the pulled resources in the response, in the place marked by an
343    * XML comment of the format <tt>&lt;!-- canonical.plugin.classname --&gt;</tt>.
344    * </p>
345    *
346    * @see com.xpn.xwiki.plugin.XWikiDefaultPlugin#endParsing(String, XWikiContext)
347    */
 
348  34459 toggle @Override
349    public String endParsing(String content, XWikiContext context)
350    {
351    // Using an XML comment is pretty safe, as extensions probably wouldn't work in other type
352    // of documents, like RTF, CSV or JSON.
353  34462 String hook = "<!-- " + this.getClass().getCanonicalName() + " -->";
354  34464 String result = content.replaceFirst(hook, getImportString(context));
355  34464 return result;
356    }
357   
 
358  0 toggle @Override
359    public UsedExtension getCacheResources(XWikiContext context) {
360  0 return new CachedItem.UsedExtension(getPulledResources(context),
361    new HashMap<String, Map<String, Object>>(getParametersMap(context)));
362    }
363   
 
364  0 toggle @Override
365    public void restoreCacheResources(XWikiContext context,
366    UsedExtension extension) {
367  0 getPulledResources(context).addAll(extension.resources);
368  0 getParametersMap(context).putAll(extension.parameters);
369    }
370   
371    /**
372    * Used to convert a proper Document Reference to string (standard form).
373    */
 
374  56 toggle protected EntityReferenceSerializer<String> getDefaultEntityReferenceSerializer()
375    {
376  56 if (this.defaultEntityReferenceSerializer == null) {
377  4 this.defaultEntityReferenceSerializer = Utils.getComponent(EntityReferenceSerializer.TYPE_STRING);
378    }
379   
380  56 return this.defaultEntityReferenceSerializer;
381    }
382   
383    /**
384    * Used to resolve a document string reference to a Document Reference.
385    */
 
386  17295 toggle protected DocumentReferenceResolver<String> getCurrentDocumentReferenceResolver()
387    {
388  17295 if (this.currentDocumentReferenceResolver == null) {
389  43 this.currentDocumentReferenceResolver =
390    Utils.getComponent(DocumentReferenceResolver.TYPE_STRING, "current");
391    }
392   
393  17295 return this.currentDocumentReferenceResolver;
394    }
395    }