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

File DefaultModelBridge.java

 

Coverage histogram

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

Code metrics

54
236
20
1
681
486
70
0.3
11.8
20
3.5

Classes

Class Line # Actions
DefaultModelBridge 68 236 0% 70 46
0.851612985.2%
 

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;
21   
22    import java.util.ArrayList;
23    import java.util.Collections;
24    import java.util.List;
25    import java.util.Map;
26    import java.util.regex.Pattern;
27   
28    import javax.inject.Inject;
29    import javax.inject.Named;
30    import javax.inject.Provider;
31    import javax.inject.Singleton;
32   
33    import org.apache.commons.lang3.exception.ExceptionUtils;
34    import org.slf4j.Logger;
35    import org.xwiki.component.annotation.Component;
36    import org.xwiki.job.api.AbstractCheckRightsRequest;
37    import org.xwiki.job.event.status.JobProgressManager;
38    import org.xwiki.model.EntityType;
39    import org.xwiki.model.reference.DocumentReference;
40    import org.xwiki.model.reference.DocumentReferenceResolver;
41    import org.xwiki.model.reference.EntityReference;
42    import org.xwiki.model.reference.EntityReferenceProvider;
43    import org.xwiki.model.reference.EntityReferenceResolver;
44    import org.xwiki.model.reference.EntityReferenceSerializer;
45    import org.xwiki.model.reference.LocalDocumentReference;
46    import org.xwiki.model.reference.SpaceReference;
47    import org.xwiki.query.Query;
48    import org.xwiki.query.QueryManager;
49    import org.xwiki.refactoring.internal.job.PermanentlyDeleteJob;
50   
51    import com.xpn.xwiki.XWiki;
52    import com.xpn.xwiki.XWikiContext;
53    import com.xpn.xwiki.XWikiException;
54    import com.xpn.xwiki.api.DeletedDocument;
55    import com.xpn.xwiki.doc.XWikiDeletedDocument;
56    import com.xpn.xwiki.doc.XWikiDocument;
57    import com.xpn.xwiki.internal.parentchild.ParentChildConfiguration;
58    import com.xpn.xwiki.store.XWikiRecycleBinStoreInterface;
59   
60    /**
61    * Default implementation of {@link ModelBridge} based on the old XWiki model.
62    *
63    * @version $Id: 4f6e8a36fcc973b28bc8f2f94c484ade7e961209 $
64    * @since 7.4M2
65    */
66    @Component
67    @Singleton
 
68    public class DefaultModelBridge implements ModelBridge
69    {
70    /**
71    * Regular expression used to match the special characters supported by the like HQL operator (plus the escaping
72    * character).
73    */
74    private static final Pattern LIKE_SPECIAL_CHARS = Pattern.compile("([%_/])");
75   
76    /**
77    * The reference to the type of object used to create an automatic redirect when renaming or moving a document.
78    */
79    private static final LocalDocumentReference REDIRECT_CLASS_REFERENCE =
80    new LocalDocumentReference(XWiki.SYSTEM_SPACE, "RedirectClass");
81   
82    @Inject
83    private Logger logger;
84   
85    /**
86    * Used to perform the low level operations on entities.
87    */
88    @Inject
89    private Provider<XWikiContext> xcontextProvider;
90   
91    /**
92    * Used to query the child documents.
93    */
94    @Inject
95    private QueryManager queryManager;
96   
97    /**
98    * Used to serialize a space reference in order to query the child documents.
99    */
100    @Inject
101    @Named("local")
102    private EntityReferenceSerializer<String> localEntityReferenceSerializer;
103   
104    /**
105    * Used to serialize the redirect location.
106    *
107    * @see #createRedirect(DocumentReference, DocumentReference)
108    */
109    @Inject
110    private EntityReferenceSerializer<String> defaultEntityReferenceSerializer;
111   
112    /**
113    * Used to resolve the references of child documents.
114    */
115    @Inject
116    @Named("explicit")
117    private DocumentReferenceResolver<String> explicitDocumentReferenceResolver;
118   
119    /**
120    * Used to create the minimum need parent reference.
121    */
122    @Inject
123    @Named("compact")
124    private EntityReferenceSerializer<String> compactEntityReferenceSerializer;
125   
126    /**
127    * Use to get back a relative reference based on a compact string reference.
128    */
129    @Inject
130    @Named("relative")
131    private EntityReferenceResolver<String> relativeStringEntityReferenceResolver;
132   
133    @Inject
134    private DocumentReferenceResolver<EntityReference> documentReferenceResolver;
135   
136    @Inject
137    private JobProgressManager progressManager;
138   
139    @Inject
140    private ParentChildConfiguration parentChildConfiguration;
141   
142    @Inject
143    private EntityReferenceProvider entityReferenceProvider;
144   
 
145  1 toggle @Override
146    public boolean create(DocumentReference documentReference)
147    {
148  1 XWikiContext xcontext = this.xcontextProvider.get();
149   
150  1 try {
151  1 XWikiDocument newDocument = xcontext.getWiki().getDocument(documentReference, xcontext);
152  1 xcontext.getWiki().saveDocument(newDocument, xcontext);
153  1 this.logger.info("Document [{}] has been created.", documentReference);
154  1 return true;
155    } catch (Exception e) {
156  0 this.logger.error("Failed to create document [{}].", documentReference, e);
157  0 return false;
158    }
159    }
160   
 
161  33 toggle @Override
162    public boolean copy(DocumentReference source, DocumentReference destination)
163    {
164  33 XWikiContext xcontext = this.xcontextProvider.get();
165  33 try {
166  33 String language = source.getLocale() != null ? source.getLocale().toString() : null;
167  33 boolean result =
168    xcontext.getWiki().copyDocument(source, destination, language, false, true, true, xcontext);
169  33 if (result) {
170  33 this.logger.info("Document [{}] has been copied to [{}].", source, destination);
171    } else {
172  0 this.logger.warn(
173    "Cannot fully copy [{}] to [{}] because an orphan translation" + " exists at the destination.",
174    source, destination);
175    }
176  33 return result;
177    } catch (Exception e) {
178  0 this.logger.error("Failed to copy [{}] to [{}].", source, destination, e);
179  0 return false;
180    }
181    }
182   
 
183  123 toggle @Override
184    public boolean delete(DocumentReference reference)
185    {
186  123 XWikiContext xcontext = this.xcontextProvider.get();
187  123 try {
188  123 XWikiDocument document = xcontext.getWiki().getDocument(reference, xcontext);
189  123 if (document.getTranslation() == 1) {
190  1 xcontext.getWiki().deleteDocument(document, xcontext);
191  1 this.logger.info("Document [{}] has been deleted.", reference);
192    } else {
193  122 xcontext.getWiki().deleteAllDocuments(document, xcontext);
194  122 this.logger.info("Document [{}] has been deleted with all its translations.", reference);
195    }
196  123 return true;
197    } catch (Exception e) {
198  0 this.logger.error("Failed to delete document [{}].", reference, e);
199  0 return false;
200    }
201    }
202   
 
203  39 toggle @Override
204    public boolean removeLock(DocumentReference reference)
205    {
206  39 XWikiContext xcontext = this.xcontextProvider.get();
207  39 try {
208  39 XWikiDocument document = xcontext.getWiki().getDocument(reference, xcontext);
209   
210  39 if (document.getLock(xcontext) != null) {
211  33 document.removeLock(xcontext);
212  33 this.logger.info("Document [{}] has been unlocked.", reference);
213    }
214   
215  39 return true;
216    } catch (Exception e) {
217    // Just warn, since it's a recoverable situation.
218  0 this.logger.warn("Failed to unlock document [{}].", reference, e);
219  0 return false;
220    }
221    }
222   
 
223  3 toggle @Override
224    public void createRedirect(DocumentReference oldReference, DocumentReference newReference)
225    {
226  3 XWikiContext xcontext = this.xcontextProvider.get();
227  3 DocumentReference redirectClassReference =
228    new DocumentReference(REDIRECT_CLASS_REFERENCE, oldReference.getWikiReference());
229  3 if (xcontext.getWiki().exists(redirectClassReference, xcontext)) {
230  3 try {
231  3 XWikiDocument oldDocument = xcontext.getWiki().getDocument(oldReference, xcontext);
232  3 int number = oldDocument.createXObject(redirectClassReference, xcontext);
233  3 String location = this.defaultEntityReferenceSerializer.serialize(newReference);
234  3 oldDocument.getXObject(redirectClassReference, number).setStringValue("location", location);
235  3 oldDocument.setHidden(true);
236  3 xcontext.getWiki().saveDocument(oldDocument, "Create automatic redirect.", xcontext);
237  3 this.logger.info("Created automatic redirect from [{}] to [{}].", oldReference, newReference);
238    } catch (XWikiException e) {
239  0 this.logger.error("Failed to create automatic redirect from [{}] to [{}].", oldReference, newReference,
240    e);
241    }
242    } else {
243  0 this.logger.warn("We can't create an automatic redirect from [{}] to [{}] because [{}] is missing.",
244    oldReference, newReference, redirectClassReference);
245    }
246    }
247   
 
248  2 toggle @Override
249    public boolean canOverwriteSilently(DocumentReference documentReference)
250    {
251  2 try {
252  2 XWikiContext xcontext = this.xcontextProvider.get();
253  2 XWikiDocument document = xcontext.getWiki().getDocument(documentReference, xcontext);
254  2 DocumentReference redirectClassReference =
255    new DocumentReference(REDIRECT_CLASS_REFERENCE, documentReference.getWikiReference());
256    // Overwrite silently the redirect pages.
257  2 return document.getXObject(redirectClassReference) != null;
258    } catch (XWikiException e) {
259  0 this.logger.warn("Failed to get document [{}]. Root cause: [{}].", documentReference,
260    ExceptionUtils.getRootCauseMessage(e));
261  0 return false;
262    }
263    }
264   
 
265  209 toggle @Override
266    public boolean exists(DocumentReference reference)
267    {
268  209 XWikiContext xcontext = this.xcontextProvider.get();
269  209 return xcontext.getWiki().exists(reference, xcontext);
270    }
271   
 
272  5 toggle @Override
273    public List<DocumentReference> getBackLinkedReferences(DocumentReference documentReference, String wikiId)
274    {
275  5 XWikiContext xcontext = this.xcontextProvider.get();
276  5 String previousWikiId = xcontext.getWikiId();
277  5 try {
278  5 xcontext.setWikiId(wikiId);
279  5 return xcontext.getWiki().getDocument(documentReference, xcontext).getBackLinkedReferences(xcontext);
280    } catch (XWikiException e) {
281  0 this.logger.error("Failed to retrieve the back-links for document [{}] on wiki [{}].", documentReference,
282    wikiId, e);
283  0 return Collections.emptyList();
284    } finally {
285  5 xcontext.setWikiId(previousWikiId);
286    }
287    }
288   
 
289  87 toggle @Override
290    public List<DocumentReference> getDocumentReferences(SpaceReference spaceReference)
291    {
292  87 try {
293    // At the moment we don't have a way to retrieve only the direct children so we select all the descendants.
294    // This means we select all the documents from the specified space and from all the nested spaces.
295  87 String statement = "select distinct(doc.fullName) from XWikiDocument as doc "
296    + "where doc.space = :space or doc.space like :spacePrefix escape '/'";
297  87 Query query = this.queryManager.createQuery(statement, Query.HQL);
298  87 query.setWiki(spaceReference.getWikiReference().getName());
299  87 String localSpaceReference = this.localEntityReferenceSerializer.serialize(spaceReference);
300  87 query.bindValue("space", localSpaceReference);
301  87 String spacePrefix = LIKE_SPECIAL_CHARS.matcher(localSpaceReference).replaceAll("/$1");
302  87 query.bindValue("spacePrefix", spacePrefix + ".%");
303   
304  87 List<DocumentReference> descendants = new ArrayList<>();
305  87 for (Object fullName : query.execute()) {
306  157 descendants.add(this.explicitDocumentReferenceResolver.resolve((String) fullName, spaceReference));
307    }
308  87 return descendants;
309    } catch (Exception e) {
310  0 this.logger.error("Failed to retrieve the documents from [{}].", spaceReference, e);
311  0 return Collections.emptyList();
312    }
313    }
314   
 
315  9 toggle @Override
316    public boolean updateParentField(final DocumentReference oldParentReference,
317    final DocumentReference newParentReference)
318    {
319  9 XWikiContext context = xcontextProvider.get();
320  9 XWiki wiki = context.getWiki();
321   
322  9 boolean popLevelProgress = false;
323  9 try {
324    // Note: This operation could have been done in Hibernate (using the Store API) in one single update query.
325    // However, due to XWiki's document cache, it`s better in the end to use the Document API and update each
326    // child document individually.
327  9 XWikiDocument oldParentDocument = wiki.getDocument(oldParentReference, context);
328   
329  9 List<DocumentReference> childReferences = oldParentDocument.getChildrenReferences(context);
330   
331  9 if (childReferences.size() > 0) {
332  2 this.progressManager.pushLevelProgress(childReferences.size(), this);
333  2 popLevelProgress = true;
334    }
335   
336  9 for (DocumentReference childReference : childReferences) {
337  8 this.progressManager.startStep(this);
338   
339  8 XWikiDocument childDocument = wiki.getDocument(childReference, context);
340  8 childDocument.setParentReference(newParentReference);
341   
342  8 wiki.saveDocument(childDocument, "Updated parent field.", true, context);
343   
344  8 this.progressManager.endStep(this);
345    }
346   
347  9 if (childReferences.size() > 0) {
348  2 this.logger.info("Document parent fields updated from [{}] to [{}] for [{}] documents.",
349    oldParentReference, newParentReference, childReferences.size());
350    }
351    } catch (Exception e) {
352  0 this.logger.error("Failed to update the document parent fields from [{}] to [{}].", oldParentReference,
353    newParentReference, e);
354  0 return false;
355    } finally {
356  9 if (popLevelProgress) {
357  2 this.progressManager.popLevelProgress(this);
358    }
359    }
360   
361  9 return true;
362    }
363   
 
364  152 toggle @Override
365    public DocumentReference setContextUserReference(DocumentReference userReference)
366    {
367  152 XWikiContext context = xcontextProvider.get();
368  152 DocumentReference previousUserReference = context.getUserReference();
369  152 context.setUserReference(userReference);
370  152 return previousUserReference;
371    }
372   
 
373  39 toggle @Override
374    public void update(DocumentReference documentReference, Map<String, String> parameters)
375    {
376  39 try {
377  39 XWikiContext context = xcontextProvider.get();
378  39 XWiki wiki = context.getWiki();
379  39 XWikiDocument document = wiki.getDocument(documentReference, context);
380  39 boolean save = false;
381   
382  39 String title = parameters.get("title");
383  39 if (title != null && !title.equals(document.getTitle())) {
384  2 document.setTitle(title);
385  2 save = true;
386    }
387   
388    // Some old applications still rely on the parent/child links between documents.
389    // For the retro-compatibility, we synchronize the "parent" field of the document with the (real)
390    // hierarchical parent.
391    //
392    // But if the user has voluntary enabled the legacy "parent/child" mechanism for the breadcrumbs, we keep
393    // the old behaviour when location and parent/child mechanism were not linked.
394    //
395    // More information: https://jira.xwiki.org/browse/XWIKI-13493
396  39 if (!parentChildConfiguration.isParentChildMechanismEnabled()) {
397   
398    // we compute the new hierarchical parent
399  38 DocumentReference hierarchicalParent = getHierarchicalParent(documentReference);
400   
401    // we compute a relative reference for the hierarchical parent
402  38 String hierarchicalParentSerialized = this.compactEntityReferenceSerializer
403    .serialize(hierarchicalParent, documentReference);
404  38 EntityReference relativeHierarchicalReference = this.relativeStringEntityReferenceResolver
405    .resolve(hierarchicalParentSerialized, EntityType.DOCUMENT);
406   
407    // we can rely on the current document parent ref, as after the copy the encoded parent is not changed.
408  38 EntityReference newDocumentParentRef = document.getRelativeParentReference();
409   
410    // all cases are supported:
411    // 1. if we move on a same wiki but on a different space: the relative ref are different, the
412    // parent will be updated
413    // 2. if we move on a same wiki, same space: the relative ref are the same, we keep the parent
414    // 3. if we move on a different wiki, different space: we don't care about the wiki, we are in the
415    // same case as 1
416    // 4. if we move on a different wiki, same space: actually we can keep the old parent relative
417    // reference, as it's relative of the document reference. So the parent reference will be well
418    // computed with the already existing relative reference.
419  38 if (!relativeHierarchicalReference.equals(newDocumentParentRef)) {
420  32 document.setParentReference(relativeHierarchicalReference);
421  32 save = true;
422    }
423    }
424   
425  39 if (save) {
426  32 wiki.saveDocument(document, "Update document after refactoring.", true, context);
427  32 this.logger.info("Document [{}] has been updated.", documentReference);
428    }
429    } catch (Exception e) {
430  0 this.logger.error("Failed to update the document [{}] after refactoring.", documentReference, e);
431    }
432    }
433   
 
434  38 toggle private DocumentReference getHierarchicalParent(DocumentReference documentReference)
435    {
436  38 final String spaceHomePage = entityReferenceProvider.getDefaultReference(EntityType.DOCUMENT).getName();
437   
438  38 EntityReference parentOfTheSpace = documentReference.getLastSpaceReference().getParent();
439   
440  38 boolean pageIsNotTerminal = documentReference.getName().equals(spaceHomePage);
441   
442    // Case 1: The document has the location A.B.C.WebHome
443    // The parent should be A.B.WebHome
444  38 if (pageIsNotTerminal && parentOfTheSpace.getType() == EntityType.SPACE) {
445  13 return new DocumentReference(spaceHomePage, new SpaceReference(parentOfTheSpace));
446    }
447   
448    // Case 2: The document has the location A.WebHome
449    // The parent should be Main.WebHome
450  25 if (pageIsNotTerminal && parentOfTheSpace.getType() == EntityType.WIKI) {
451  11 return new DocumentReference(spaceHomePage,
452    new SpaceReference(entityReferenceProvider.getDefaultReference(EntityType.SPACE).getName(),
453    documentReference.getWikiReference()));
454    }
455   
456    // Case 3: The document has the location A.B
457    // The parent should be A.WebHome
458  14 return new DocumentReference(spaceHomePage, documentReference.getLastSpaceReference());
459    }
460   
 
461  24 toggle private XWikiDeletedDocument getDeletedDocument(XWikiContext context, long deletedDocumentId) throws XWikiException
462    {
463  24 XWiki xWiki = context.getWiki();
464  24 XWikiDeletedDocument deletedDocument = xWiki.getDeletedDocument(deletedDocumentId, context);
465  24 if (deletedDocument == null) {
466  2 logger.error("Deleted document with ID [{}] does not exist.", deletedDocumentId);
467  2 return null;
468    }
469  22 return deletedDocument;
470    }
471   
 
472  8 toggle @Override
473    public boolean restoreDeletedDocument(long deletedDocumentId, AbstractCheckRightsRequest request)
474    {
475  8 XWikiContext context = this.xcontextProvider.get();
476  8 XWiki xwiki = context.getWiki();
477   
478  8 DocumentReference deletedDocumentReference = null;
479  8 try {
480    // Retrieve the deleted document by ID.
481  8 XWikiDeletedDocument deletedDocument = this.getDeletedDocument(context, deletedDocumentId);
482  8 if (deletedDocument == null) {
483  1 return false;
484    }
485  7 deletedDocumentReference = deletedDocument.getDocumentReference();
486   
487    // If the document (or the translation) that we want to restore does not exist, restore it.
488  7 if (xwiki.exists(deletedDocumentReference, context)) {
489    // TODO: Add overwrite support maybe also with interactive (question/answer) mode.
490    // Default for now is to skip and log as error to restore over existing documents.
491  1 logger.error("Document [{}] with ID [{}] can not be restored. Document already exists",
492    deletedDocument.getFullName(), deletedDocumentId);
493  6 } else if (request.isCheckAuthorRights()
494    && !canRestoreDeletedDocument(deletedDocument, context.getAuthorReference())) {
495  1 logger.error("The author [{}] of this script is not allowed to restore document [{}] with ID [{}]",
496    context.getAuthorReference(), deletedDocumentReference, deletedDocumentId);
497  5 } else if (request.isCheckRights() &&
498    !canRestoreDeletedDocument(deletedDocument, context.getUserReference())) {
499  0 logger.error("You are not allowed to restore document [{}] with ID [{}]", deletedDocumentReference,
500    deletedDocumentId);
501    } else {
502    // Restore the document.
503  5 xwiki.restoreFromRecycleBin(deletedDocument.getId(), "Restored from recycle bin", context);
504   
505  5 logger.info("Document [{}] has been restored", deletedDocumentReference);
506   
507  5 return true;
508    }
509    } catch (Exception e) {
510    // Try to log the document reference since it`s more useful than the ID.
511  0 if (deletedDocumentReference != null) {
512  0 logger.error("Failed to restore document [{}] with ID [{}]", deletedDocumentReference,
513    deletedDocumentId, e);
514    } else {
515  0 logger.error("Failed to restore deleted document with ID [{}]", deletedDocumentId, e);
516    }
517    }
518   
519  2 return false;
520    }
521   
 
522  1 toggle @Override
523    public List<Long> getDeletedDocumentIds(String batchId)
524    {
525  1 XWikiContext context = this.xcontextProvider.get();
526  1 XWiki xwiki = context.getWiki();
527   
528  1 List<Long> result = new ArrayList<>();
529  1 try {
530  1 XWikiDeletedDocument[] deletedDocuments =
531    xwiki.getRecycleBinStore().getAllDeletedDocuments(batchId, false, context, true);
532  1 for (XWikiDeletedDocument deletedDocument : deletedDocuments) {
533  2 result.add(deletedDocument.getId());
534    }
535    } catch (Exception e) {
536  0 logger.error("Failed to get deleted document IDs for batch [{}]", batchId);
537    }
538   
539  1 return result;
540    }
541   
 
542  5 toggle protected boolean canRestoreDeletedDocument(XWikiDeletedDocument deletedDocument, DocumentReference userReference)
543    {
544  5 boolean result = false;
545   
546  5 XWikiContext context = this.xcontextProvider.get();
547   
548    // Remember the context user.
549  5 DocumentReference currentUserReference = context.getUserReference();
550  5 try {
551    // Reuse the DeletedDocument API to check rights.
552  5 DeletedDocument deletedDocumentApi = new DeletedDocument(deletedDocument, context);
553   
554    // Note: DeletedDocument API works with the current context user.
555  5 context.setUserReference(userReference);
556   
557  5 result = deletedDocumentApi.canUndelete();
558    } catch (Exception e) {
559  0 logger.error("Failed to check restore rights on deleted document [{}] for user [{}]",
560    deletedDocument.getId(), userReference, e);
561    } finally {
562    // Restore the context user;
563  5 context.setUserReference(currentUserReference);
564    }
565   
566  5 return result;
567    }
568   
 
569  1 toggle protected boolean canPermanentlyDeleteDocument(XWikiDeletedDocument deletedDocument, DocumentReference userReference)
570    {
571  1 boolean result = false;
572   
573  1 XWikiContext context = this.xcontextProvider.get();
574  1 XWiki xwiki = context.getWiki();
575   
576    // Remember the context user.
577  1 DocumentReference currentUserReference = context.getUserReference();
578  1 try {
579    // Reuse the DeletedDocument API to check rights.
580  1 DeletedDocument deletedDocumentApi = new DeletedDocument(deletedDocument, context);
581   
582    // Note: DeletedDocument API works with the current context user.
583  1 context.setUserReference(userReference);
584   
585  1 result = deletedDocumentApi.canDelete();
586    } catch (Exception e) {
587  0 logger.error("Failed to check delete rights on deleted document [{}] for user [{}]",
588    deletedDocument.getId(), userReference, e);
589    } finally {
590    // Restore the context user;
591  1 context.setUserReference(currentUserReference);
592    }
593   
594  1 return result;
595    }
596   
 
597  16 toggle @Override
598    public boolean permanentlyDeleteDocument(long deletedDocumentId, AbstractCheckRightsRequest request)
599    {
600  16 XWikiContext context = this.xcontextProvider.get();
601  16 XWiki xwiki = context.getWiki();
602   
603  16 DocumentReference deletedDocumentReference = null;
604  16 try {
605    // Retrieve the deleted document by ID.
606  16 XWikiDeletedDocument deletedDocument = this.getDeletedDocument(context, deletedDocumentId);
607   
608  16 if (deletedDocument == null) {
609  1 return false;
610    }
611  15 deletedDocumentReference = deletedDocument.getDocumentReference();
612   
613  15 if (request.isCheckRights() && !canPermanentlyDeleteDocument(deletedDocument, context.getUserReference())) {
614  1 logger.error("You are not allowed to permanently delete document [{}] with ID [{}]",
615    deletedDocumentReference, deletedDocumentId);
616  14 } else if (request.isCheckAuthorRights()
617    && !canPermanentlyDeleteDocument(deletedDocument, context.getAuthorReference())) {
618  0 logger.error("The author [{}] of this script is not allowed to permanently deleted document [{}] with "
619    + "id", context.getAuthorReference(), deletedDocumentReference, deletedDocumentId);
620    } else {
621    // Restore the document.
622  14 xwiki.getRecycleBinStore().deleteFromRecycleBin(deletedDocumentId, context, true);
623   
624  14 logger.info("Document [{}] has been permanently deleted.", deletedDocumentReference);
625   
626  14 return true;
627    }
628    } catch (Exception e) {
629    // Try to log the document reference since it`s more useful than the ID.
630  0 if (deletedDocumentReference != null) {
631  0 logger.error("Failed to permanently delete document [{}] with ID [{}]", deletedDocumentReference,
632    deletedDocumentId, e);
633    } else {
634  0 logger.error("Failed to permanently delete document with ID [{}]", deletedDocumentId, e);
635    }
636    }
637   
638  1 return false;
639    }
640   
 
641  1 toggle @Override
642    public boolean permanentlyDeleteAllDocuments(PermanentlyDeleteJob deleteJob, AbstractCheckRightsRequest request)
643    {
644  1 XWikiContext context = this.xcontextProvider.get();
645  1 XWiki xwiki = context.getWiki();
646  1 int limit = 10;
647   
648  1 XWikiRecycleBinStoreInterface recycleBinStore = xwiki.getRecycleBinStore();
649  1 try {
650  1 long numberOfDocumentsToDelete = recycleBinStore.getNumberOfDeletedDocuments(context);
651   
652  1 int nbDocToDelete;
653  1 if (numberOfDocumentsToDelete > Integer.MAX_VALUE) {
654  0 logger.warn("Only [{}] file can be deleted at once. Please run again the job to delete everything.",
655    Integer.MAX_VALUE);
656  0 nbDocToDelete = Integer.MAX_VALUE;
657    } else {
658  1 nbDocToDelete = Integer.valueOf(numberOfDocumentsToDelete + "");
659    }
660  1 progressManager.pushLevelProgress(nbDocToDelete, deleteJob);
661   
662  3 for (int i = 0; i < nbDocToDelete; i += limit) {
663  2 Long[] allDeletedDocumentsIds = recycleBinStore.getAllDeletedDocumentsIds(context, limit);
664  2 for (Long deletedDocumentsId : allDeletedDocumentsIds) {
665  12 if (deleteJob.getStatus().isCanceled()) {
666  0 return false;
667    } else {
668  12 this.progressManager.startStep(deleteJob);
669  12 this.permanentlyDeleteDocument(deletedDocumentsId, request);
670  12 this.progressManager.endStep(deleteJob);
671    }
672    }
673    }
674  1 return true;
675    } catch (XWikiException e) {
676  0 logger.error("Failed to permanently delete all documents", e);
677    }
678   
679  0 return false;
680    }
681    }