1. Project Clover database Sat Feb 2 2019 06:45:20 CET
  2. Package org.xwiki.refactoring.internal.job

File AbstractCopyOrMoveJob.java

 

Coverage histogram

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

Code metrics

72
150
18
1
400
301
71
0.47
8.33
18
3.94

Classes

Class Line # Actions
AbstractCopyOrMoveJob 49 150 0% 71 29
0.8791666687.9%
 

Contributing tests

This file is covered by 29 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.job;
21   
22    import java.util.Collection;
23    import java.util.Collections;
24    import java.util.LinkedList;
25    import java.util.List;
26   
27    import javax.inject.Inject;
28   
29    import org.xwiki.model.EntityType;
30    import org.xwiki.model.reference.DocumentReference;
31    import org.xwiki.model.reference.EntityReference;
32    import org.xwiki.model.reference.SpaceReference;
33    import org.xwiki.refactoring.internal.LinkRefactoring;
34    import org.xwiki.refactoring.job.AbstractCopyOrMoveRequest;
35    import org.xwiki.refactoring.job.EntityJobStatus;
36    import org.xwiki.refactoring.job.OverwriteQuestion;
37    import org.xwiki.refactoring.job.question.EntitySelection;
38    import org.xwiki.security.authorization.Right;
39    import org.xwiki.wiki.descriptor.WikiDescriptorManager;
40    import org.xwiki.wiki.manager.WikiManagerException;
41   
42    /**
43    * Defines the common methods for both copy and move jobs since they are both related to moving entities.
44    * @param <T> the type of the request associated to the job
45    *
46    * @since 10.11RC1
47    * @version $Id: d9ff06d774b967f163242ba5ec9dfebbc8d243b6 $
48    */
 
49    public abstract class AbstractCopyOrMoveJob<T extends AbstractCopyOrMoveRequest> extends
50    AbstractEntityJobWithChecks<T, EntityJobStatus<T>>
51    {
52    /**
53    * Specifies whether all entities with the same name are to be overwritten on not. When {@code true} all entities
54    * with the same name are overwritten. When {@code false} all entities with the same name are skipped. If
55    * {@code null} then a question is asked for each entity.
56    */
57    private Boolean overwriteAll;
58   
59    /**
60    * The component used to refactor document links after a document is rename or moved.
61    */
62    @Inject
63    private LinkRefactoring linkRefactoring;
64   
65    @Inject
66    private WikiDescriptorManager wikiDescriptorManager;
67   
 
68  60 toggle @Override
69    protected void runInternal() throws Exception
70    {
71  60 if (this.request.getDestination() != null) {
72  60 super.runInternal();
73    }
74    }
75   
76    /**
77    * If true, we check that the source and destination entityReference are of the same type.
78    * And we process them using that information.
79    * @return false by default.
80    */
 
81  36 toggle protected boolean processOnlySameSourceDestinationTypes()
82    {
83  36 return false;
84    }
85   
 
86  58 toggle @Override
87    protected void process(EntityReference source)
88    {
89    // Perform generic checks that don't depend on the source/destination type.
90   
91  58 EntityReference destination = this.request.getDestination();
92   
93  58 if (processOnlySameSourceDestinationTypes()) {
94  37 if (source.getType() != destination.getType()) {
95  1 this.logger.error("You cannot change the entity type (from [{}] to [{}]).", source.getType(),
96    destination.getType());
97  1 return;
98    }
99    }
100   
101  57 if (isDescendantOrSelf(destination, source)) {
102  1 this.logger.error("Cannot make [{}] a descendant of itself.", source);
103  1 return;
104    }
105   
106  56 if (source.getParent() != null && source.getParent().equals(destination)) {
107  1 this.logger.error("Cannot move [{}] into [{}], it's already there.", source, destination);
108  1 return;
109    }
110   
111    // Dispatch the move operation based on the source entity type.
112   
113  55 switch (source.getType()) {
114  51 case DOCUMENT:
115  51 process(new DocumentReference(source), destination);
116  51 break;
117  3 case SPACE:
118  3 process(new SpaceReference(source), destination);
119  3 break;
120  1 default:
121  1 this.logger.error("Unsupported source entity type [{}].", source.getType());
122    }
123    }
124   
 
125  57 toggle private boolean isDescendantOrSelf(EntityReference alice, EntityReference bob)
126    {
127  57 EntityReference parent = alice;
128  222 while (parent != null && !parent.equals(bob)) {
129  165 parent = parent.getParent();
130    }
131  57 return parent != null;
132    }
133   
 
134  51 toggle protected void process(DocumentReference source, EntityReference destination)
135    {
136  51 if (processOnlySameSourceDestinationTypes()) {
137    // We know the destination is a document (see above).
138  35 DocumentReference destinationDocumentReference = new DocumentReference(destination);
139  35 this.process(source, destinationDocumentReference);
140    } else {
141  16 if (this.request.isDeep() && isSpaceHomeReference(source)) {
142  1 process(source.getLastSpaceReference(), destination);
143  15 } else if (destination.getType() == EntityType.SPACE) {
144  10 maybeMove(source, new DocumentReference(source.getName(), new SpaceReference(destination)));
145  5 } else if (destination.getType() == EntityType.DOCUMENT
146    && isSpaceHomeReference(new DocumentReference(destination))) {
147  2 maybeMove(source, new DocumentReference(source.getName(), new SpaceReference(destination.getParent())));
148    } else {
149  3 this.logger.error("Unsupported destination entity type [{}] for a document.", destination.getType());
150    }
151    }
152    }
153   
 
154  35 toggle protected void process(DocumentReference source, DocumentReference destination)
155    {
156  35 if (this.request.isDeep() && isSpaceHomeReference(source)) {
157  3 if (isSpaceHomeReference(destination)) {
158    // Rename an entire space.
159  2 process(source.getLastSpaceReference(), destination.getLastSpaceReference());
160    } else {
161  1 this.logger.error("You cannot transform a non-terminal document [{}] into a terminal document [{}]"
162    + " and preserve its child documents at the same time.", source, destination);
163    }
164    } else {
165  32 maybeMove(source, destination);
166    }
167    }
168   
 
169  4 toggle protected void process(SpaceReference source, EntityReference destination)
170    {
171  4 if (processOnlySameSourceDestinationTypes()) {
172    // We know the destination is a space (see above).
173  1 process(source, new SpaceReference(destination));
174    } else {
175  3 if (destination.getType() == EntityType.SPACE || destination.getType() == EntityType.WIKI) {
176  1 process(source, new SpaceReference(source.getName(), destination));
177  2 } else if (destination.getType() == EntityType.DOCUMENT
178    && isSpaceHomeReference(new DocumentReference(destination))) {
179  1 process(source, new SpaceReference(source.getName(), destination.getParent()));
180    } else {
181  1 this.logger.error("Unsupported destination entity type [{}] for a space.", destination.getType());
182    }
183    }
184    }
185   
 
186  5 toggle protected void process(final SpaceReference source, final SpaceReference destination)
187    {
188  5 visitDocuments(source, new Visitor<DocumentReference>()
189    {
 
190  4 toggle @Override
191    public void visit(DocumentReference oldChildReference)
192    {
193  4 DocumentReference newChildReference = oldChildReference.replaceParent(source, destination);
194  4 maybeMove(oldChildReference, newChildReference);
195    }
196    });
197    }
198   
 
199  46 toggle private boolean checkAllRights(DocumentReference oldReference, DocumentReference newReference)
200    {
201  46 if (isDeleteSources() && !hasAccess(Right.DELETE, oldReference)) {
202    // The move operation is implemented as Copy + Delete.
203  2 this.logger.error("You are not allowed to delete [{}].", oldReference);
204  2 return false;
205  44 } else if (!hasAccess(Right.VIEW, oldReference)) {
206  2 this.logger.error("You don't have sufficient permissions over the source document [{}].",
207    oldReference);
208  2 return false;
209  42 } else if (!hasAccess(Right.EDIT, newReference)
210    || (this.modelBridge.exists(newReference) && !hasAccess(Right.DELETE, newReference)))
211    {
212  2 this.logger.error("You don't have sufficient permissions over the destination document [{}].",
213    newReference);
214  2 return false;
215    }
216  40 return true;
217    }
218   
 
219  48 toggle protected void maybeMove(DocumentReference oldReference, DocumentReference newReference)
220    {
221    // Perform checks that are specific to the document source/destination type.
222   
223  48 EntitySelection entitySelection = this.concernedEntities.get(oldReference);
224  48 if (entitySelection != null && !entitySelection.isSelected()) {
225    // TODO: handle entitySelection == null which means something is wrong
226  1 this.logger.info("Skipping [{}] because it has been unselected.", oldReference);
227  47 } else if (!this.modelBridge.exists(oldReference)) {
228  1 this.logger.warn("Skipping [{}] because it doesn't exist.", oldReference);
229  46 } else if (this.checkAllRights(oldReference, newReference)) {
230  40 move(oldReference, newReference);
231    }
232    }
233   
 
234  40 toggle private void move(DocumentReference oldReference, DocumentReference newReference)
235    {
236  40 this.progressManager.pushLevelProgress(7, this);
237   
238  40 try {
239    // Step 1: Delete the destination document if needed.
240  40 this.progressManager.startStep(this);
241  40 if (this.modelBridge.exists(newReference)) {
242  2 if (this.request.isInteractive() && !this.modelBridge.canOverwriteSilently(newReference)
243    && !confirmOverwrite(oldReference, newReference)) {
244  0 this.logger.warn(
245    "Skipping [{}] because [{}] already exists and the user doesn't want to overwrite it.",
246    oldReference, newReference);
247  0 return;
248  2 } else if (!this.modelBridge.delete(newReference)) {
249  0 return;
250    }
251    }
252  40 this.progressManager.endStep(this);
253   
254    // Step 2: Copy the source document to the destination.
255  40 this.progressManager.startStep(this);
256  40 if (!this.modelBridge.copy(oldReference, newReference)) {
257  2 return;
258    }
259  38 this.progressManager.endStep(this);
260   
261    // Step 3: Update the destination document based on the source document parameters.
262  38 this.progressManager.startStep(this);
263  38 this.modelBridge.update(newReference, this.request.getEntityParameters(oldReference));
264  38 this.progressManager.endStep(this);
265   
266    // Step 4 + 5: Update other documents that might be affected by this move.
267  38 updateDocuments(oldReference, newReference);
268   
269    // Step 6: Delete the source document.
270  38 this.progressManager.startStep(this);
271  38 if (isDeleteSources()) {
272  11 this.modelBridge.delete(oldReference);
273    }
274  38 this.progressManager.endStep(this);
275   
276  38 this.postMove(oldReference, newReference);
277    } finally {
278  40 this.progressManager.popLevelProgress(this);
279    }
280    }
281   
282    protected abstract void postMove(DocumentReference oldReference, DocumentReference newReference);
283   
 
284  38 toggle private void updateDocuments(DocumentReference oldReference, DocumentReference newReference)
285    {
286    // Step 3: Update the links.
287  38 this.progressManager.startStep(this);
288  38 if (this.request.isUpdateLinks()) {
289  34 updateLinks(oldReference, newReference);
290    }
291  38 this.progressManager.endStep(this);
292   
293   
294  38 this.postUpdateDocuments(oldReference, newReference);
295    }
296   
297    protected abstract void postUpdateDocuments(DocumentReference oldReference, DocumentReference newReference);
298   
 
299  0 toggle private boolean confirmOverwrite(EntityReference source, EntityReference destination)
300    {
301  0 if (this.overwriteAll == null) {
302  0 OverwriteQuestion question = new OverwriteQuestion(source, destination);
303  0 try {
304  0 this.status.ask(question);
305  0 if (this.status.isCanceled()) {
306  0 return false;
307  0 } else if (!question.isAskAgain()) {
308    // Use the same answer for the following overwrite questions.
309  0 this.overwriteAll = question.isOverwrite();
310    }
311  0 return question.isOverwrite();
312    } catch (InterruptedException e) {
313  0 this.logger.warn("Overwrite question has been interrupted.");
314  0 return false;
315    }
316    } else {
317  0 return this.overwriteAll;
318    }
319    }
320   
 
321  34 toggle private void updateLinks(DocumentReference oldReference, DocumentReference newReference)
322    {
323  34 this.progressManager.pushLevelProgress(2, this);
324   
325  34 try {
326    // Step 1: Update the links that target the old reference to point to the new reference.
327  34 this.progressManager.startStep(this);
328  34 if (this.isDeleteSources()) {
329  7 updateBackLinks(oldReference, newReference);
330    }
331  34 this.progressManager.endStep(this);
332   
333    // Step 2: Update the relative links from the document content.
334  34 this.progressManager.startStep(this);
335  34 this.linkRefactoring.updateRelativeLinks(oldReference, newReference);
336    } finally {
337  34 this.progressManager.popLevelProgress(this);
338    }
339    }
340   
 
341  7 toggle private void updateBackLinks(DocumentReference oldReference, DocumentReference newReference)
342    {
343  7 Collection<String> wikiIds = Collections.singleton(oldReference.getWikiReference().getName());
344  7 if (this.request.isUpdateLinksOnFarm()) {
345  1 try {
346  1 wikiIds = this.wikiDescriptorManager.getAllIds();
347    } catch (WikiManagerException e) {
348  0 this.logger.error("Failed to retrieve the list of wikis.", e);
349    }
350    }
351  7 boolean popLevelProgress = false;
352  7 try {
353  7 if (wikiIds.size() > 0) {
354  7 this.progressManager.pushLevelProgress(wikiIds.size(), this);
355  7 popLevelProgress = true;
356    }
357  7 for (String wikiId : wikiIds) {
358  8 this.progressManager.startStep(this);
359  8 updateBackLinks(oldReference, newReference, wikiId);
360  8 this.progressManager.endStep(this);
361    }
362    } finally {
363  7 if (popLevelProgress) {
364  7 this.progressManager.popLevelProgress(this);
365    }
366    }
367    }
368   
 
369  8 toggle private void updateBackLinks(DocumentReference oldReference, DocumentReference newReference, String wikiId)
370    {
371  8 this.logger.info("Updating the back-links for document [{}] in wiki [{}].", oldReference, wikiId);
372  8 List<DocumentReference> backlinkDocumentReferences =
373    this.modelBridge.getBackLinkedReferences(oldReference, wikiId);
374  8 this.progressManager.pushLevelProgress(backlinkDocumentReferences.size(), this);
375   
376  8 try {
377  8 for (DocumentReference backlinkDocumentReference : backlinkDocumentReferences) {
378  4 this.progressManager.startStep(this);
379  4 if (hasAccess(Right.EDIT, backlinkDocumentReference)) {
380  4 this.linkRefactoring.renameLinks(backlinkDocumentReference, oldReference, newReference);
381    }
382  4 this.progressManager.endStep(this);
383    }
384    } finally {
385  8 this.progressManager.popLevelProgress(this);
386    }
387    }
388   
 
389  63 toggle @Override
390    protected EntityReference getCommonParent()
391    {
392  63 if (this.request.isUpdateLinksOnFarm()) {
393  2 return null;
394    } else {
395  61 List<EntityReference> entityReferences = new LinkedList<>(this.request.getEntityReferences());
396  61 entityReferences.add(this.request.getDestination());
397  61 return getCommonParent(entityReferences);
398    }
399    }
400    }