1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package org.xwiki.webjars.script

File WebJarsScriptService.java

 

Coverage histogram

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

Code metrics

28
65
10
1
337
162
26
0.4
6.5
10
2.6

Classes

Class Line # Actions
WebJarsScriptService 58 65 0% 26 13
0.873786487.4%
 

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 org.xwiki.webjars.script;
21   
22    import java.util.ArrayList;
23    import java.util.Arrays;
24    import java.util.Collections;
25    import java.util.LinkedHashMap;
26    import java.util.List;
27    import java.util.Map;
28   
29    import javax.inject.Inject;
30    import javax.inject.Named;
31    import javax.inject.Singleton;
32   
33    import org.apache.commons.lang3.StringUtils;
34    import org.apache.commons.lang3.exception.ExceptionUtils;
35    import org.slf4j.Logger;
36    import org.xwiki.component.annotation.Component;
37    import org.xwiki.extension.Extension;
38    import org.xwiki.extension.repository.CoreExtensionRepository;
39    import org.xwiki.extension.repository.InstalledExtensionRepository;
40    import org.xwiki.resource.ResourceReference;
41    import org.xwiki.resource.ResourceReferenceSerializer;
42    import org.xwiki.resource.SerializeResourceReferenceException;
43    import org.xwiki.resource.UnsupportedResourceReferenceException;
44    import org.xwiki.script.service.ScriptService;
45    import org.xwiki.url.ExtendedURL;
46    import org.xwiki.webjars.internal.WebJarsResourceReference;
47    import org.xwiki.wiki.descriptor.WikiDescriptorManager;
48   
49    /**
50    * Make it easy to use WebJars in scripts. For example it can compute an XWiki WebJars URL.
51    *
52    * @version $Id: 3bc56e019280ffef28aeca37cc9ed50e0cf99237 $
53    * @since 6.0M1
54    */
55    @Component
56    @Named("webjars")
57    @Singleton
 
58    public class WebJarsScriptService implements ScriptService
59    {
60    private static final String RESOURCE_SEPARATOR = "/";
61   
62    /**
63    * The name of the parameter that specifies the WebJar version.
64    */
65    private static final String VERSION = "version";
66   
67    /**
68    * The name of the parameter that specifies the wiki in which the resource is located. If not specified then
69    * the wiki used will be the current wiki.
70    */
71    private static final String WIKI = "wiki";
72   
73    /**
74    * The default {@code groupId} for Maven projects that produce WebJars.
75    */
76    private static final String DEFAULT_WEBJAR_GROUP_ID = "org.webjars";
77   
78    @Inject
79    private Logger logger;
80   
81    /**
82    * Used to check if the WebJar is a core extension and to get its version.
83    */
84    @Inject
85    private CoreExtensionRepository coreExtensionRepository;
86   
87    /**
88    * Used to check if the WebJar is an installed extension and to get its version.
89    */
90    @Inject
91    private InstalledExtensionRepository installedExtensionRepository;
92   
93    /**
94    * Used to get the id of the current wiki in order to determine the current extension namespace.
95    */
96    @Inject
97    private WikiDescriptorManager wikiDescriptorManager;
98   
99    @Inject
100    private ResourceReferenceSerializer<ResourceReference, ExtendedURL> defaultResourceReferenceSerializer;
101   
102    /**
103    * Creates an URL that can be used to load a resource (JavaScript, CSS, etc.) from a WebJar in the current wiki.
104    *
105    * @param resourceName the resource asked using the format {@code <webjarId>/<version>/<path/to/resource>}
106    * (e.g. {@code angular/2.1.11/angular.js"})
107    * @return the computed URL
108    */
 
109  1066 toggle public String url(String resourceName)
110    {
111  1066 if (StringUtils.isEmpty(resourceName)) {
112  0 return null;
113    }
114   
115  1066 String[] parts = resourceName.split(RESOURCE_SEPARATOR, 3);
116  1066 if (parts.length < 3) {
117  0 logger.warn("Invalid webjar resource name [{}]. Expected format is 'webjarId/version/path'", resourceName);
118  0 return null;
119    }
120   
121    // Prefix the webjarId with a fake groupId just to make sure that the colon character (:) is not interpreted as
122    // separator in the webjarId. This is required in order to ensure that the behavior of this method doesn't
123    // change. Note that the groupdId is ignored if the WebJar version is specified so the fake groupId won't have
124    // any effect.
125  1066 return url("fakeGroupId:" + parts[0], null, parts[2], Collections.singletonMap(VERSION, parts[1]));
126    }
127   
128    /**
129    * Creates an URL that can be used to load a resource (JavaScript, CSS, etc.) from a WebJar in the current wiki.
130    *
131    * @param webjarId the id of the WebJar that contains the resource; the format of the WebJar id is
132    * {@code groupId:artifactId} (e.g. {@code org.xwiki.platform:xwiki-platform-job-webjar}), where the
133    * {@code groupId} can be omitted if it is {@link #DEFAULT_WEBJAR_GROUP_ID} (i.e. {@code angular}
134    * translates to {@code org.webjars:angular})
135    * @param path the path within the WebJar, starting from the version folder (e.g. you should pass just
136    * {@code angular.js} if the actual path is {@code META-INF/resources/webjars/angular/2.1.11/angular.js})
137    * @return the URL to load the WebJar resource (relative to the context path of the web application)
138    */
 
139  23052 toggle public String url(String webjarId, String path)
140    {
141  23052 return url(webjarId, null, path, null);
142    }
143   
144    /**
145    * Creates an URL that can be used to load a resource (JavaScript, CSS, etc.) from a WebJar in the passed namespace.
146    *
147    * @param webjarId the id of the WebJar that contains the resource; the format of the WebJar id is
148    * {@code groupId:artifactId} (e.g. {@code org.xwiki.platform:xwiki-platform-job-webjar}), where the
149    * {@code groupId} can be omitted if it is {@link #DEFAULT_WEBJAR_GROUP_ID} (i.e. {@code angular}
150    * translates to {@code org.webjars:angular})
151    * @param namespace the namespace in which the webjars resources will be loaded from (e.g. for a wiki namespace you
152    * should use the format {@code wiki:<wikiId>}). If null then defaults to the current wiki
153    * namespace. And if the passed namespace doesn't exist, falls back to the main wiki namespace
154    * @param path the path within the WebJar, starting from the version folder (e.g. you should pass just
155    * {@code angular.js} if the actual path is {@code META-INF/resources/webjars/angular/2.1.11/angular.js})
156    * @return the URL to load the WebJar resource (relative to the context path of the web application)
157    * @since 8.1M2
158    */
 
159  0 toggle public String url(String webjarId, String namespace, String path)
160    {
161  0 return url(webjarId, namespace, path, null);
162    }
163   
164    /**
165    * Creates an URL that can be used to load a resource (JavaScript, CSS, etc.) from a WebJar.
166    *
167    * @param webjarId the id of the WebJar that contains the resource; the format of the WebJar id is
168    * {@code groupId:artifactId} (e.g. {@code org.xwiki.platform:xwiki-platform-job-webjar}), where the
169    * {@code groupId} can be omitted if it is {@link #DEFAULT_WEBJAR_GROUP_ID} (i.e. {@code angular}
170    * translates to {@code org.webjars:angular})
171    * @param path the path within the WebJar, starting from the version folder (e.g. you should pass just
172    * {@code angular.js} if the actual path is {@code META-INF/resources/webjars/angular/2.1.11/angular.js})
173    * @param params additional query string parameters to add to the returned URL; there are two known (reserved)
174    * parameters: {@code version} (the WebJar version) and {@code evaluate} (a boolean parameter that
175    * specifies if the requested resource has Velocity code that needs to be evaluated); besides these you
176    * can pass whatever parameters you like (they will be taken into account or not depending on the
177    * resource)
178    * @return the URL to load the WebJar resource (relative to the context path of the web application)
179    */
 
180  1603 toggle public String url(String webjarId, String path, Map<String, ?> params)
181    {
182    // For backward-compatibility reasons, we still support passing the target wiki in parameters
183  1603 String namespace = null;
184  1603 if (params != null) {
185    // For backward-compatibility reasons we still support passing the target wiki in parameters
186  1603 String wikiId = (String) params.get(WIKI);
187  1603 if (!StringUtils.isEmpty(wikiId)) {
188  2 namespace = constructNamespace(wikiId);
189    }
190    }
191   
192  1603 return url(webjarId, namespace, path, params);
193    }
194   
195    /**
196    * Creates an URL that can be used to load a resource (JavaScript, CSS, etc.) from a WebJar in the passed namespace.
197    *
198    * @param webjarId the id of the WebJar that contains the resource; the format of the WebJar id is
199    * {@code groupId:artifactId} (e.g. {@code org.xwiki.platform:xwiki-platform-job-webjar}), where the
200    * {@code groupId} can be omitted if it is {@link #DEFAULT_WEBJAR_GROUP_ID} (i.e. {@code angular}
201    * translates to {@code org.webjars:angular})
202    * @param namespace the namespace in which the webjars resources will be loaded from (e.g. for a wiki namespace you
203    * should use the format {@code wiki:<wikiId>}). If null then defaults to the current wiki
204    * namespace. And if the passed namespace doesn't exist, falls back to the main wiki namespace
205    * @param path the path within the WebJar, starting from the version folder (e.g. you should pass just
206    * {@code angular.js} if the actual path is {@code META-INF/resources/webjars/angular/2.1.11/angular.js})
207    * @param params additional query string parameters to add to the returned URL; there are two known (reserved)
208    * parameters: {@code version} (the WebJar version) and {@code evaluate} (a boolean parameter that
209    * specifies if the requested resource has Velocity code that needs to be evaluated); besides these you
210    * can pass whatever parameters you like (they will be taken into account or not depending on the
211    * resource)
212    * @return the URL to load the WebJar resource (relative to the context path of the web application)
213    * @since 8.1M2
214    */
 
215  25721 toggle public String url(String webjarId, String namespace, String path, Map<String, ?> params)
216    {
217  25722 if (StringUtils.isEmpty(webjarId)) {
218  0 return null;
219    }
220   
221  25722 String groupId = DEFAULT_WEBJAR_GROUP_ID;
222  25722 String artifactId = webjarId;
223  25722 int groupSeparatorPosition = webjarId.indexOf(':');
224  25722 if (groupSeparatorPosition >= 0) {
225    // A different group id.
226  14891 groupId = webjarId.substring(0, groupSeparatorPosition);
227  14891 artifactId = webjarId.substring(groupSeparatorPosition + 1);
228    }
229   
230  25722 Map<String, Object> urlParams = new LinkedHashMap<>();
231  25721 if (params != null) {
232  2670 urlParams.putAll(params);
233    }
234   
235    // For backward-compatibility reasons we still support passing the target wiki in parameters. However we need
236    // to remove it from the params if that's the case since we don't want to output a URL with the wiki id in the
237    // query string (since the namespace is now part of the URL).
238  25721 urlParams.remove(WIKI);
239   
240  25721 Object version = urlParams.remove(VERSION);
241  25722 if (version == null) {
242    // Try to determine the version based on the extensions that are currently installed or provided by default.
243  24654 version = getVersion(String.format("%s:%s", groupId, artifactId), namespace);
244    }
245   
246    // Construct a WebJarsResourceReference so that we can serialize it!
247  25722 WebJarsResourceReference resourceReference =
248    getResourceReference(artifactId, version, namespace, path, urlParams);
249   
250  25722 ExtendedURL extendedURL;
251  25722 try {
252  25722 extendedURL = this.defaultResourceReferenceSerializer.serialize(resourceReference);
253    } catch (SerializeResourceReferenceException | UnsupportedResourceReferenceException e) {
254  0 this.logger.warn("Error while serializing WebJar URL for id [{}], path = [{}]. Root cause = [{}]",
255    webjarId, path, ExceptionUtils.getRootCauseMessage(e));
256  0 return null;
257    }
258   
259  25721 return extendedURL.serialize();
260    }
261   
 
262  25722 toggle private WebJarsResourceReference getResourceReference(String artifactId, Object version, String namespace,
263    String path, Map<String, Object> urlParams)
264    {
265  25721 List<String> segments = new ArrayList<>();
266  25722 segments.add(artifactId);
267    // Don't include the version if it's not specified and there's no installed/core extension that matches the
268    // given WebJar id.
269  25722 if (version != null) {
270  25430 segments.add((String) version);
271    }
272  25721 segments.addAll(Arrays.asList(path.split(RESOURCE_SEPARATOR)));
273   
274    // When a JavaScript resource is loaded using RequireJS the resource URL must not include the ".js" suffix (by
275    // default) if the URL is relative and doesn't have a query string. Before XWIKI-10881 (Introduce a proper
276    // "webjars" type instead of reusing the "bin" type) all WebJar URLs had a query string (the resource path) so
277    // we were forced to specify the ".js" suffix when using RequireJS. The resource path is currently no longer
278    // part of the query string and thus the ".js" suffix must now be omitted (otherwise RequireJS will ask for
279    // ".js.js"), unless the resource has parameters (e.g. the resource is evaluated). In order to preserve
280    // backwards compatibility with existing extensions and also in order to fix this mess (the developer doesn't
281    // know when to put the ".js" suffix and when not) we have decided to add a fake query string if the ".js"
282    // suffix is specified and there is no query string (thus preventing RequireJS from requesting ".js.js").
283  25722 if (path.endsWith(".js") && urlParams.isEmpty()) {
284  7445 urlParams.put("r", "1");
285    }
286   
287  25721 WebJarsResourceReference resourceReference =
288    new WebJarsResourceReference(resolveNamespace(namespace), segments);
289  25720 for (Map.Entry<String, Object> parameterEntry : urlParams.entrySet()) {
290  9046 resourceReference.addParameter(parameterEntry.getKey(), parameterEntry.getValue());
291    }
292   
293  25722 return resourceReference;
294    }
295   
 
296  24655 toggle private String getVersion(String extensionId, String namespace)
297    {
298    // Look for WebJars that are core extensions.
299  24654 Extension extension = this.coreExtensionRepository.getCoreExtension(extensionId);
300  24655 if (extension == null) {
301    // Look for WebJars that are installed on the passed namespace (if defined), the current wiki or the main
302    // wiki.
303  296 String selectedNamespace = resolveNamespace(namespace);
304  296 extension = this.installedExtensionRepository.getInstalledExtension(extensionId, selectedNamespace);
305  296 if (extension == null) {
306    // Fallback by looking in the main wiki
307  292 selectedNamespace = constructNamespace(this.wikiDescriptorManager.getMainWikiId());
308  292 extension = this.installedExtensionRepository.getInstalledExtension(extensionId, selectedNamespace);
309  292 if (extension == null) {
310  292 return null;
311    }
312    }
313    }
314  24362 return extension.getId().getVersion().getValue();
315    }
316   
 
317  26017 toggle private String resolveNamespace(String namespace)
318    {
319  26016 String resolvedNamespace;
320  26018 if (StringUtils.isNotEmpty(namespace)) {
321  5 resolvedNamespace = namespace;
322    } else {
323  26013 resolvedNamespace = constructNamespace(getCurrentWikiId());
324    }
325  26018 return resolvedNamespace;
326    }
327   
 
328  26011 toggle private String getCurrentWikiId()
329    {
330  26012 return this.wikiDescriptorManager.getCurrentWikiId();
331    }
332   
 
333  26307 toggle private String constructNamespace(String wikiId)
334    {
335  26307 return String.format("wiki:%s", wikiId);
336    }
337    }