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

File DefaultLinkRefactoring.java

 

Coverage histogram

../../../../img/srcFileCovDistChart8.png
54% of files have more coverage

Code metrics

26
86
6
1
323
201
24
0.28
14.33
6
4

Classes

Class Line # Actions
DefaultLinkRefactoring 60 86 0% 24 23
0.8050847680.5%
 

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.refactoring.internal;
21   
22    import java.util.List;
23    import java.util.Locale;
24   
25    import javax.inject.Inject;
26    import javax.inject.Named;
27    import javax.inject.Provider;
28    import javax.inject.Singleton;
29   
30    import org.slf4j.Logger;
31    import org.xwiki.component.annotation.Component;
32    import org.xwiki.component.manager.ComponentManager;
33    import org.xwiki.job.event.status.JobProgressManager;
34    import org.xwiki.model.EntityType;
35    import org.xwiki.model.reference.DocumentReference;
36    import org.xwiki.model.reference.DocumentReferenceResolver;
37    import org.xwiki.model.reference.EntityReference;
38    import org.xwiki.model.reference.EntityReferenceResolver;
39    import org.xwiki.model.reference.EntityReferenceSerializer;
40    import org.xwiki.rendering.block.Block;
41    import org.xwiki.rendering.block.XDOM;
42    import org.xwiki.rendering.listener.reference.ResourceReference;
43    import org.xwiki.rendering.listener.reference.ResourceType;
44    import org.xwiki.rendering.renderer.BlockRenderer;
45   
46    import com.xpn.xwiki.XWiki;
47    import com.xpn.xwiki.XWikiContext;
48    import com.xpn.xwiki.XWikiException;
49    import com.xpn.xwiki.doc.XWikiDocument;
50    import com.xpn.xwiki.internal.render.LinkedResourceHelper;
51   
52    /**
53    * Default implementation of {@link LinkRefactoring}.
54    *
55    * @version $Id: 0aa3a869dfbe1c2161fde2feed3505e9ef63e671 $
56    * @since 7.4M2
57    */
58    @Component
59    @Singleton
 
60    public class DefaultLinkRefactoring implements LinkRefactoring
61    {
62    @Inject
63    private Logger logger;
64   
65    /**
66    * Used to perform the low level operations on entities.
67    */
68    @Inject
69    private Provider<XWikiContext> xcontextProvider;
70   
71    /**
72    * The component used to record the progress.
73    */
74    @Inject
75    private JobProgressManager progressManager;
76   
77    /**
78    * Used to serialize link references.
79    *
80    * @see #updateRelativeLinks(XWikiDocument, DocumentReference)
81    */
82    @Inject
83    @Named("compact")
84    private EntityReferenceSerializer<String> compactEntityReferenceSerializer;
85   
86    @Inject
87    private DocumentReferenceResolver<EntityReference> defaultReferenceDocumentReferenceResolver;
88   
89    @Inject
90    private EntityReferenceResolver<ResourceReference> resourceReferenceResolver;
91   
92    /**
93    * Used to get a {@link BlockRenderer} dynamically.
94    *
95    * @see #updateRelativeLinks(XWikiDocument, DocumentReference)
96    */
97    @Inject
98    @Named("context")
99    private Provider<ComponentManager> contextComponentManagerProvider;
100   
101    @Inject
102    private LinkedResourceHelper linkedResourceHelper;
103   
 
104  6 toggle @Override
105    public void renameLinks(DocumentReference documentReference, DocumentReference oldLinkTarget,
106    DocumentReference newLinkTarget)
107    {
108  6 boolean popLevelProgress = false;
109  6 try {
110  6 XWikiContext xcontext = this.xcontextProvider.get();
111  6 XWikiDocument document = xcontext.getWiki().getDocument(documentReference, xcontext);
112  6 List<Locale> locales = document.getTranslationLocales(xcontext);
113   
114  6 this.progressManager.pushLevelProgress(1 + locales.size(), this);
115  6 popLevelProgress = true;
116   
117    // Update the default locale instance.
118  6 this.progressManager.startStep(this);
119  6 renameLinks(document, oldLinkTarget, newLinkTarget);
120   
121    // Update the translations.
122  6 for (Locale locale : locales) {
123  0 this.progressManager.startStep(this);
124  0 renameLinks(document.getTranslatedDocument(locale, xcontext), oldLinkTarget, newLinkTarget);
125    }
126    } catch (XWikiException e) {
127  0 this.logger.error("Failed to rename the links that target [{}] from [{}].", oldLinkTarget,
128    documentReference, e);
129    } finally {
130  6 if (popLevelProgress) {
131  6 this.progressManager.popLevelProgress(this);
132    }
133    }
134    }
135   
 
136  6 toggle private void renameLinks(XWikiDocument document, DocumentReference oldTarget, DocumentReference newTarget)
137    throws XWikiException
138    {
139  6 DocumentReference currentDocumentReference = document.getDocumentReference();
140   
141    // We support only the syntaxes for which there is an available renderer.
142  6 if (!this.contextComponentManagerProvider.get().hasComponent(BlockRenderer.class,
143    document.getSyntax().toIdString())) {
144  0 this.logger.warn("We can't rename the links from [{}] "
145    + "because there is no renderer available for its syntax [{}].", currentDocumentReference,
146    document.getSyntax());
147  0 return;
148    }
149   
150  6 XDOM xdom = document.getXDOM();
151  6 List<Block> blocks = linkedResourceHelper.getBlocks(xdom);
152   
153  6 boolean modified = false;
154  6 for (Block block : blocks) {
155  11 try {
156  11 modified |= renameLink(block, currentDocumentReference, oldTarget, newTarget);
157    } catch (IllegalArgumentException e) {
158  0 continue;
159    }
160    }
161   
162  6 if (modified) {
163  6 document.setContent(xdom);
164  6 saveDocumentPreservingContentAuthor(document, "Renamed back-links.", false);
165  6 this.logger.info("The links from [{}] that were targeting [{}] have been updated to target [{}].",
166    document.getDocumentReferenceWithLocale(), oldTarget, newTarget);
167    } else {
168  0 this.logger.info("No back-links to update in [{}].", currentDocumentReference);
169    }
170    }
171   
 
172  11 toggle private boolean renameLink(Block block, DocumentReference currentDocumentReference, DocumentReference oldTarget,
173    DocumentReference newTarget) throws IllegalArgumentException
174    {
175  11 boolean modified = false;
176   
177  11 ResourceReference resourceReference = linkedResourceHelper.getResourceReference(block);
178  11 if (resourceReference == null) {
179    // Skip invalid blocks.
180  0 throw new IllegalArgumentException();
181    }
182   
183  11 ResourceType resourceType = resourceReference.getType();
184   
185    // TODO: support ATTACHMENT as well.
186  11 if (!ResourceType.DOCUMENT.equals(resourceType) && !ResourceType.SPACE.equals(resourceType)) {
187    // We are currently only interested in Document or Space references.
188  0 throw new IllegalArgumentException();
189    }
190   
191    // Resolve the resource reference.
192  11 EntityReference linkEntityReference =
193    resourceReferenceResolver.resolve(resourceReference, null, currentDocumentReference);
194    // Resolve the document of the reference.
195  11 DocumentReference linkTargetDocumentReference =
196    defaultReferenceDocumentReferenceResolver.resolve(linkEntityReference);
197  11 EntityReference newTargetReference = newTarget;
198  11 ResourceType newResourceType = resourceType;
199   
200    // If the link was resolved to a space...
201  11 if (EntityType.SPACE.equals(linkEntityReference.getType())) {
202  2 if (XWiki.DEFAULT_SPACE_HOMEPAGE.equals(newTarget.getName())) {
203    // If the new document reference is also a space (non-terminal doc), be careful to keep it
204    // serialized as a space still (i.e. without ".WebHome") and not serialize it as a doc by mistake
205    // (i.e. with ".WebHome").
206  1 newTargetReference = newTarget.getLastSpaceReference();
207    } else {
208    // If the new target is a non-terminal document, we can not use a "space:" resource type to access
209    // it anymore. To fix it, we need to change the resource type of the link reference "doc:".
210  1 newResourceType = ResourceType.DOCUMENT;
211    }
212    }
213   
214    // If the link targets the old (renamed) document reference, we must update it.
215  11 if (linkTargetDocumentReference.equals(oldTarget)) {
216  11 modified = true;
217  11 String newReferenceString =
218    this.compactEntityReferenceSerializer.serialize(newTargetReference, currentDocumentReference);
219   
220    // Update the reference in the XDOM.
221  11 linkedResourceHelper.setResourceReferenceString(block, newReferenceString);
222  11 linkedResourceHelper.setResourceType(block, newResourceType);
223    }
224   
225  11 return modified;
226    }
227   
 
228  5 toggle @Override
229    public void updateRelativeLinks(DocumentReference oldReference, DocumentReference newReference)
230    {
231  5 XWikiContext xcontext = this.xcontextProvider.get();
232  5 try {
233  5 updateRelativeLinks(xcontext.getWiki().getDocument(newReference, xcontext), oldReference);
234    } catch (XWikiException e) {
235  0 this.logger.error("Failed to update the relative links from [{}].", newReference, e);
236    }
237    }
238   
 
239  5 toggle private void updateRelativeLinks(XWikiDocument document, DocumentReference oldDocumentReference)
240    throws XWikiException
241    {
242    // We support only the syntaxes for which there is an available renderer.
243  5 if (!this.contextComponentManagerProvider.get().hasComponent(BlockRenderer.class,
244    document.getSyntax().toIdString())) {
245  0 this.logger.warn("We can't update the relative links from [{}]"
246    + " because there is no renderer available for its syntax [{}].", document.getDocumentReference(),
247    document.getSyntax());
248  0 return;
249    }
250   
251  5 DocumentReference newDocumentReference = document.getDocumentReference();
252   
253  5 XDOM xdom = document.getXDOM();
254  5 List<Block> blocks = linkedResourceHelper.getBlocks(xdom);
255   
256  5 boolean modified = false;
257  5 for (Block block : blocks) {
258  4 ResourceReference resourceReference = linkedResourceHelper.getResourceReference(block);
259  4 if (resourceReference == null) {
260    // Skip invalid blocks.
261  0 continue;
262    }
263   
264  4 ResourceType resourceType = resourceReference.getType();
265   
266    // TODO: support ATTACHMENT as well.
267  4 if (!ResourceType.DOCUMENT.equals(resourceType) && !ResourceType.SPACE.equals(resourceType)) {
268    // We are currently only interested in Document or Space references.
269  0 continue;
270    }
271   
272    // current link, use the old document's reference to fill in blanks.
273  4 EntityReference oldLinkReference =
274    this.resourceReferenceResolver.resolve(resourceReference, null, oldDocumentReference);
275    // new link, use the new document's reference to fill in blanks.
276  4 EntityReference newLinkReference =
277    this.resourceReferenceResolver.resolve(resourceReference, null, newDocumentReference);
278   
279    // If the new and old link references don`t match, then we must update the relative link.
280  4 if (!newLinkReference.equals(oldLinkReference)) {
281  3 modified = true;
282   
283    // Serialize the old (original) link relative to the new document's location, in compact form.
284  3 String serializedLinkReference =
285    this.compactEntityReferenceSerializer.serialize(oldLinkReference, newDocumentReference);
286   
287    // Update the reference in the XDOM.
288  3 linkedResourceHelper.setResourceReferenceString(block, serializedLinkReference);
289    }
290    }
291   
292  5 if (modified) {
293  2 document.setContent(xdom);
294  2 saveDocumentPreservingContentAuthor(document, "Updated the relative links.", true);
295  2 this.logger.info("Updated the relative links from [{}].", document.getDocumentReference());
296    } else {
297  3 this.logger.info("No relative links to update in [{}].", document.getDocumentReference());
298    }
299    }
300   
301    /**
302    * HACK: Save the given document without changing the content author because the document may loose or win
303    * programming and script rights as a consequence and this is not the intent of this operation. Even though the
304    * document content field was modified, the change is purely syntactic; the semantic is not affected so it's not
305    * clear whether the content author deserves to be updated or not (even without the side effects).
306    *
307    * @param document the document to be saved
308    * @param comment the revision comment
309    * @param minorEdit whether it's a minor edit or not
310    * @throws XWikiException if saving the document fails
311    */
 
312  8 toggle private void saveDocumentPreservingContentAuthor(XWikiDocument document, String comment, boolean minorEdit)
313    throws XWikiException
314    {
315  8 XWikiContext xcontext = this.xcontextProvider.get();
316    // Preserve the content author.
317  8 document.setContentDirty(false);
318    // Make sure the version is incremented.
319  8 document.setMetaDataDirty(true);
320  8 document.setAuthorReference(xcontext.getUserReference());
321  8 xcontext.getWiki().saveDocument(document, comment, minorEdit, xcontext);
322    }
323    }