1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package org.xwiki.rendering.internal.wiki

File XWikiWikiModel.java

 

Coverage histogram

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

Code metrics

34
69
8
1
341
191
29
0.42
8.62
8
3.62

Classes

Class Line # Actions
XWikiWikiModel 69 69 0% 29 12
0.891891989.2%
 

Contributing tests

This file is covered by 15 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.rendering.internal.wiki;
21   
22    import java.io.StringReader;
23    import java.io.UnsupportedEncodingException;
24    import java.net.URLEncoder;
25    import java.nio.charset.Charset;
26    import java.util.ArrayList;
27    import java.util.LinkedHashMap;
28    import java.util.List;
29    import java.util.Map;
30   
31    import javax.inject.Inject;
32    import javax.inject.Named;
33    import javax.inject.Singleton;
34   
35    import org.apache.commons.lang3.StringUtils;
36    import org.apache.http.NameValuePair;
37    import org.apache.http.client.utils.URLEncodedUtils;
38    import org.apache.http.message.BasicNameValuePair;
39    import org.slf4j.Logger;
40    import org.w3c.css.sac.InputSource;
41    import org.w3c.dom.css.CSSStyleDeclaration;
42    import org.xwiki.bridge.DocumentAccessBridge;
43    import org.xwiki.bridge.SkinAccessBridge;
44    import org.xwiki.component.annotation.Component;
45    import org.xwiki.model.EntityType;
46    import org.xwiki.model.reference.AttachmentReference;
47    import org.xwiki.model.reference.DocumentReference;
48    import org.xwiki.model.reference.EntityReference;
49    import org.xwiki.model.reference.EntityReferenceResolver;
50    import org.xwiki.model.reference.EntityReferenceSerializer;
51    import org.xwiki.rendering.configuration.ExtendedRenderingConfiguration;
52    import org.xwiki.rendering.listener.reference.AttachmentResourceReference;
53    import org.xwiki.rendering.listener.reference.DocumentResourceReference;
54    import org.xwiki.rendering.listener.reference.ResourceReference;
55    import org.xwiki.rendering.listener.reference.ResourceType;
56    import org.xwiki.rendering.wiki.WikiModel;
57   
58    import com.steadystate.css.parser.CSSOMParser;
59    import com.steadystate.css.parser.SACParserCSS21;
60   
61    /**
62    * Implementation using the Document Access Bridge ({@link DocumentAccessBridge}).
63    *
64    * @version $Id: 86d498258dce87924f80af50ff8e584dfae0fd19 $
65    * @since 2.0M1
66    */
67    @Component
68    @Singleton
 
69    public class XWikiWikiModel implements WikiModel
70    {
71    /**
72    * The suffix used to mark an amount of pixels.
73    */
74    private static final String PIXELS = "px";
75   
76    /**
77    * The name of the {@code width} image parameter.
78    */
79    private static final String WIDTH = "width";
80   
81    /**
82    * The name of the {@code height} image parameter.
83    */
84    private static final String HEIGHT = "height";
85   
86    /**
87    * The UTF-8 encoding.
88    */
89    private static final Charset UTF8 = Charset.forName("UTF-8");
90   
91    /**
92    * The component used to access configuration parameters.
93    */
94    @Inject
95    private ExtendedRenderingConfiguration extendedRenderingConfiguration;
96   
97    /**
98    * The component used to access the underlying XWiki model.
99    */
100    @Inject
101    private DocumentAccessBridge documentAccessBridge;
102   
103    /**
104    * Used to find the URL for an icon.
105    */
106    @Inject
107    private SkinAccessBridge skinAccessBridge;
108   
109    /**
110    * The component used to serialize entity references to strings.
111    */
112    @Inject
113    @Named("compactwiki")
114    private EntityReferenceSerializer<String> compactEntityReferenceSerializer;
115   
116    @Inject
117    private EntityReferenceResolver<ResourceReference> resourceReferenceEntityReferenceResolver;
118   
119    /**
120    * Provides logging for this class.
121    */
122    @Inject
123    private Logger logger;
124   
125    /**
126    * The object used to parse the CSS from the image style parameter.
127    * <p>
128    * NOTE: We explicitly pass the CSS SAC parser because otherwise (e.g. using the default constructor)
129    * {@link CSSOMParser} sets the {@code org.w3c.css.sac.parser} system property to its own implementation, i.e.
130    * {@link com.steadystate.css.parser.SACParserCSS2}, affecting other components that require a CSS SAC parser (e.g.
131    * PDF export).
132    *
133    * @see <a href="http://jira.xwiki.org/jira/browse/XWIKI-5625">XWIKI-5625: PDF styling doesn't work anymore</a>
134    */
135    private final CSSOMParser cssParser = new CSSOMParser(new SACParserCSS21());
136   
137    /**
138    * {@inheritDoc}
139    *
140    * @since 2.5RC1
141    */
 
142  286 toggle @Override
143    public String getLinkURL(ResourceReference linkReference)
144    {
145    // Note that we don't ask for a full URL because links should be relative as much as possible
146  286 EntityReference attachmentReference =
147    resourceReferenceEntityReferenceResolver.resolve(linkReference, EntityType.ATTACHMENT);
148   
149  286 if (attachmentReference == null) {
150  0 throw new IllegalArgumentException(String.valueOf(attachmentReference));
151    }
152   
153  286 return this.documentAccessBridge.getAttachmentURL(new AttachmentReference(attachmentReference),
154    linkReference.getParameter(AttachmentResourceReference.QUERY_STRING), false);
155    }
156   
157    /**
158    * {@inheritDoc}
159    *
160    * @since 2.5RC1
161    */
 
162  304 toggle @Override
163    public String getImageURL(ResourceReference imageReference, Map<String, String> parameters)
164    {
165    // Handle icon references
166  304 if (imageReference.getType().equals(ResourceType.ICON)) {
167  20 return this.skinAccessBridge.getIconURL(imageReference.getReference());
168    }
169   
170    // Handle attachment references
171  284 if (this.extendedRenderingConfiguration.isImageDimensionsIncludedInImageURL()) {
172  283 Map<String, Object> urlParameters = getImageURLParameters(parameters);
173  283 if (!urlParameters.isEmpty()) {
174    // Handle scaled image attachments.
175  59 String queryString = imageReference.getParameter(AttachmentResourceReference.QUERY_STRING);
176  59 queryString = extendQueryString(queryString, urlParameters);
177  59 ResourceReference scaledImageReference = imageReference.clone();
178  59 scaledImageReference.setParameter(AttachmentResourceReference.QUERY_STRING, queryString);
179  59 return getLinkURL(scaledImageReference);
180    }
181    }
182   
183  225 return getLinkURL(imageReference);
184    }
185   
 
186  258 toggle @Override
187    public boolean isDocumentAvailable(ResourceReference resourceReference)
188    {
189  258 EntityReference documentReference =
190    resourceReferenceEntityReferenceResolver.resolve(resourceReference, EntityType.DOCUMENT);
191   
192  258 if (documentReference == null) {
193  0 throw new IllegalArgumentException(String.valueOf(resourceReference));
194    }
195   
196  258 return this.documentAccessBridge.exists(new DocumentReference(documentReference));
197    }
198   
 
199  245 toggle @Override
200    public String getDocumentViewURL(ResourceReference resourceReference)
201    {
202  245 EntityReference documentReference =
203    resourceReferenceEntityReferenceResolver.resolve(resourceReference, EntityType.DOCUMENT);
204   
205  245 if (documentReference == null) {
206  0 throw new IllegalArgumentException(String.valueOf(resourceReference));
207    }
208   
209  245 return this.documentAccessBridge.getDocumentURL(new DocumentReference(documentReference), "view",
210    resourceReference.getParameter(DocumentResourceReference.QUERY_STRING),
211    resourceReference.getParameter(DocumentResourceReference.ANCHOR));
212    }
213   
 
214  16 toggle @Override
215    public String getDocumentEditURL(ResourceReference resourceReference)
216    {
217    // Add the parent=<current document name> parameter to the query string of the edit URL so that
218    // the new document is created with the current page as its parent.
219  16 String modifiedQueryString = resourceReference.getParameter(DocumentResourceReference.QUERY_STRING);
220  16 if (StringUtils.isBlank(modifiedQueryString)) {
221  16 DocumentReference reference = this.documentAccessBridge.getCurrentDocumentReference();
222  16 if (reference != null) {
223  16 try {
224    // Note 1: we encode using UTF8 since it's the W3C recommendation.
225    // See http://www.w3.org/TR/html40/appendix/notes.html#non-ascii-chars
226   
227    // Note 2: We need to be careful to use a compact serializer so that the wiki part is not
228    // part of the generated String so that when the user clicks on the link, the new page is created
229    // with a relative parent (and thus the new page can be moved from one wiki to another easily
230    // without having to change the parent reference).
231   
232    // TODO: Once the xwiki-url module is usable, refactor this code to use it and remove the need to
233    // perform explicit encoding here.
234   
235  16 modifiedQueryString = "parent=" + URLEncoder.encode(
236    this.compactEntityReferenceSerializer.serialize(reference), UTF8.name());
237    } catch (UnsupportedEncodingException e) {
238    // Not supporting UTF-8 as a valid encoding for some reasons. We consider XWiki cannot work
239    // without that encoding.
240  0 throw new RuntimeException("Failed to URL encode [" + reference + "] using UTF-8.", e);
241    }
242    }
243    }
244   
245  16 EntityReference documentReference =
246    resourceReferenceEntityReferenceResolver.resolve(resourceReference, EntityType.DOCUMENT);
247   
248  16 if (documentReference == null) {
249  0 throw new IllegalArgumentException(String.valueOf(resourceReference));
250    }
251   
252  16 return this.documentAccessBridge.getDocumentURL(new DocumentReference(documentReference), "create",
253    modifiedQueryString, resourceReference.getParameter(DocumentResourceReference.ANCHOR));
254    }
255   
256    /**
257    * Extracts the specified image dimension from the image parameters.
258    *
259    * @param dimension either {@code width} or {@code height}
260    * @param imageParameters the image parameters; may include the {@code width}, {@code height} and {@code style}
261    * parameters
262    * @return the value of the passed dimension if it is specified in the image parameters, {@code null} otherwise
263    */
 
264  566 toggle private String getImageDimension(String dimension, Map<String, String> imageParameters)
265    {
266    // Check first if the style parameter contains information about the given dimension. In-line style has priority
267    // over the dimension parameters.
268  566 String value = null;
269  566 String style = imageParameters.get("style");
270  566 if (StringUtils.isNotBlank(style)) {
271  10 try {
272  10 CSSStyleDeclaration sd = this.cssParser.parseStyleDeclaration(new InputSource(new StringReader(style)));
273  10 value = sd.getPropertyValue(dimension);
274    } catch (Exception e) {
275    // Ignore the style parameter but log a warning to let the user know.
276  0 this.logger.warn("Failed to parse CSS style [{}]", style);
277    }
278    }
279  566 if (StringUtils.isBlank(value)) {
280    // Fall back on the value of the dimension parameter.
281  559 value = imageParameters.get(dimension);
282    }
283  566 return value;
284    }
285   
286    /**
287    * Creates the parameters that can be added to an image URL to resize the image on the server side.
288    *
289    * @param imageParameters image parameters, including width and height when they are specified
290    * @return the parameters to be added to an image URL in order to resize the image on the server side
291    */
 
292  283 toggle private Map<String, Object> getImageURLParameters(Map<String, String> imageParameters)
293    {
294  283 String width = StringUtils.removeEnd(getImageDimension(WIDTH, imageParameters), PIXELS);
295  283 String height = StringUtils.removeEnd(getImageDimension(HEIGHT, imageParameters), PIXELS);
296  283 boolean useHeight = StringUtils.isNotEmpty(height) && StringUtils.isNumeric(height);
297  283 Map<String, Object> queryString = new LinkedHashMap<String, Object>();
298  283 if (StringUtils.isEmpty(width) || !StringUtils.isNumeric(width)) {
299    // Width is unspecified or is not measured in pixels.
300  228 if (useHeight) {
301    // Height is specified in pixels.
302  2 queryString.put(HEIGHT, height);
303    } else {
304    // If image width and height are unspecified or if they are not expressed in pixels then limit the image
305    // size to best fit the rectangle specified in the configuration (keeping aspect ratio).
306  226 int widthLimit = this.extendedRenderingConfiguration.getImageWidthLimit();
307  226 if (widthLimit > 0) {
308  2 queryString.put(WIDTH, widthLimit);
309    }
310  226 int heightLimit = this.extendedRenderingConfiguration.getImageHeightLimit();
311  226 if (heightLimit > 0) {
312  1 queryString.put(HEIGHT, heightLimit);
313    }
314  226 if (widthLimit > 0 && heightLimit > 0) {
315  1 queryString.put("keepAspectRatio", true);
316    }
317    }
318    } else {
319    // Width is specified in pixels.
320  55 queryString.put(WIDTH, width);
321  55 if (useHeight) {
322    // Height is specified in pixels.
323  3 queryString.put(HEIGHT, height);
324    }
325    }
326  283 return queryString;
327    }
328   
 
329  59 toggle private String extendQueryString(String queryString, Map<String, Object> parameters)
330    {
331  59 List<NameValuePair> pairs = new ArrayList<NameValuePair>(URLEncodedUtils.parse(queryString, UTF8, '&'));
332    // Exclude the parameters that are already on the query string.
333  59 for (NameValuePair pair : pairs) {
334  2 parameters.remove(pair.getName());
335    }
336  59 for (Map.Entry<String, Object> entry : parameters.entrySet()) {
337  63 pairs.add(new BasicNameValuePair(entry.getKey(), String.valueOf(entry.getValue())));
338    }
339  59 return URLEncodedUtils.format(pairs, UTF8);
340    }
341    }