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

File DefaultModelBridge.java

 

Coverage histogram

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

Code metrics

28
123
12
1
391
275
38
0.31
10.25
12
3.17

Classes

Class Line # Actions
DefaultModelBridge 61 123 0% 38 21
0.8711656387.1%
 

Contributing tests

This file is covered by 13 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.slf4j.Logger;
34    import org.xwiki.component.annotation.Component;
35    import org.xwiki.job.event.status.JobProgressManager;
36    import org.xwiki.model.EntityType;
37    import org.xwiki.model.reference.DocumentReference;
38    import org.xwiki.model.reference.DocumentReferenceResolver;
39    import org.xwiki.model.reference.EntityReference;
40    import org.xwiki.model.reference.EntityReferenceProvider;
41    import org.xwiki.model.reference.EntityReferenceSerializer;
42    import org.xwiki.model.reference.LocalDocumentReference;
43    import org.xwiki.model.reference.SpaceReference;
44    import org.xwiki.query.Query;
45    import org.xwiki.query.QueryManager;
46   
47    import com.xpn.xwiki.XWiki;
48    import com.xpn.xwiki.XWikiContext;
49    import com.xpn.xwiki.XWikiException;
50    import com.xpn.xwiki.doc.XWikiDocument;
51    import com.xpn.xwiki.internal.parentchild.ParentChildConfiguration;
52   
53    /**
54    * Default implementation of {@link ModelBridge} based on the old XWiki model.
55    *
56    * @version $Id: da70a7d1aa1f173990922113b470de5ab482dc7b $
57    * @since 7.4M2
58    */
59    @Component
60    @Singleton
 
61    public class DefaultModelBridge implements ModelBridge
62    {
63    /**
64    * Regular expression used to match the special characters supported by the like HQL operator (plus the escaping
65    * character).
66    */
67    private static final Pattern LIKE_SPECIAL_CHARS = Pattern.compile("([%_/])");
68   
69    /**
70    * The reference to the type of object used to create an automatic redirect when renaming or moving a document.
71    */
72    private static final LocalDocumentReference REDIRECT_CLASS_REFERENCE = new LocalDocumentReference(
73    XWiki.SYSTEM_SPACE, "RedirectClass");
74   
75    @Inject
76    private Logger logger;
77   
78    /**
79    * Used to perform the low level operations on entities.
80    */
81    @Inject
82    private Provider<XWikiContext> xcontextProvider;
83   
84    /**
85    * Used to query the child documents.
86    */
87    @Inject
88    private QueryManager queryManager;
89   
90    /**
91    * Used to serialize a space reference in order to query the child documents.
92    */
93    @Inject
94    @Named("local")
95    private EntityReferenceSerializer<String> localEntityReferenceSerializer;
96   
97    /**
98    * Used to serialize the redirect location.
99    *
100    * @see #createRedirect(DocumentReference, DocumentReference)
101    */
102    @Inject
103    private EntityReferenceSerializer<String> defaultEntityReferenceSerializer;
104   
105    /**
106    * Used to resolve the references of child documents.
107    */
108    @Inject
109    @Named("explicit")
110    private DocumentReferenceResolver<String> explicitDocumentReferenceResolver;
111   
112    @Inject
113    private JobProgressManager progressManager;
114   
115    @Inject
116    private ParentChildConfiguration parentChildConfiguration;
117   
118    @Inject
119    private EntityReferenceProvider entityReferenceProvider;
120   
 
121  1 toggle @Override
122    public boolean create(DocumentReference documentReference)
123    {
124  1 XWikiContext xcontext = this.xcontextProvider.get();
125   
126  1 try {
127  1 XWikiDocument newDocument = xcontext.getWiki().getDocument(documentReference, xcontext);
128  1 xcontext.getWiki().saveDocument(newDocument, xcontext);
129  1 this.logger.info("Document [{}] has been created.", documentReference);
130  1 return true;
131    } catch (Exception e) {
132  0 this.logger.error("Failed to create document [{}].", documentReference, e);
133  0 return false;
134    }
135    }
136   
 
137  4 toggle @Override
138    public boolean copy(DocumentReference source, DocumentReference destination)
139    {
140  4 XWikiContext xcontext = this.xcontextProvider.get();
141  4 try {
142  4 String language = source.getLocale() != null ? source.getLocale().toString() : null;
143  4 boolean result =
144    xcontext.getWiki().copyDocument(source, destination, language, false, true, true, xcontext);
145  4 if (result) {
146  4 this.logger.info("Document [{}] has been copied to [{}].", source, destination);
147    } else {
148  0 this.logger.warn("Cannot fully copy [{}] to [{}] because an orphan translation"
149    + " exists at the destination.", source, destination);
150    }
151  4 return result;
152    } catch (Exception e) {
153  0 this.logger.error("Failed to copy [{}] to [{}].", source, destination, e);
154  0 return false;
155    }
156    }
157   
 
158  27 toggle @Override
159    public boolean delete(DocumentReference reference)
160    {
161  27 XWikiContext xcontext = this.xcontextProvider.get();
162  27 try {
163  27 XWikiDocument document = xcontext.getWiki().getDocument(reference, xcontext);
164  27 if (document.getTranslation() == 1) {
165  1 xcontext.getWiki().deleteDocument(document, xcontext);
166  1 this.logger.info("Document [{}] has been deleted.", reference);
167    } else {
168  26 xcontext.getWiki().deleteAllDocuments(document, xcontext);
169  26 this.logger.info("Document [{}] has been deleted with all its translations.", reference);
170    }
171  27 return true;
172    } catch (Exception e) {
173  0 this.logger.error("Failed to delete document [{}].", reference, e);
174  0 return false;
175    }
176    }
177   
 
178  17 toggle @Override
179    public boolean removeLock(DocumentReference reference)
180    {
181  17 XWikiContext xcontext = this.xcontextProvider.get();
182  17 try {
183  17 XWikiDocument document = xcontext.getWiki().getDocument(reference, xcontext);
184   
185  17 if (document.getLock(xcontext) != null) {
186  14 document.removeLock(xcontext);
187  14 this.logger.info("Document [{}] has been unlocked.", reference);
188    }
189   
190  17 return true;
191    } catch (Exception e) {
192    // Just warn, since it's a recoverable situation.
193  0 this.logger.warn("Failed to unlock document [{}].", reference, e);
194  0 return false;
195    }
196    }
197   
 
198  3 toggle @Override
199    public void createRedirect(DocumentReference oldReference, DocumentReference newReference)
200    {
201  3 XWikiContext xcontext = this.xcontextProvider.get();
202  3 DocumentReference redirectClassReference =
203    new DocumentReference(REDIRECT_CLASS_REFERENCE, oldReference.getWikiReference());
204  3 if (xcontext.getWiki().exists(redirectClassReference, xcontext)) {
205  3 try {
206  3 XWikiDocument oldDocument = xcontext.getWiki().getDocument(oldReference, xcontext);
207  3 int number = oldDocument.createXObject(redirectClassReference, xcontext);
208  3 String location = this.defaultEntityReferenceSerializer.serialize(newReference);
209  3 oldDocument.getXObject(redirectClassReference, number).setStringValue("location", location);
210  3 oldDocument.setHidden(true);
211  3 xcontext.getWiki().saveDocument(oldDocument, "Create automatic redirect.", xcontext);
212  3 this.logger.info("Created automatic redirect from [{}] to [{}].", oldReference, newReference);
213    } catch (XWikiException e) {
214  0 this.logger.error("Failed to create automatic redirect from [{}] to [{}].", oldReference, newReference,
215    e);
216    }
217    } else {
218  0 this.logger.warn("We can't create an automatic redirect from [{}] to [{}] because [{}] is missing.",
219    oldReference, newReference, redirectClassReference);
220    }
221    }
222   
 
223  31 toggle @Override
224    public boolean exists(DocumentReference reference)
225    {
226  31 XWikiContext xcontext = this.xcontextProvider.get();
227  31 return xcontext.getWiki().exists(reference, xcontext);
228    }
229   
 
230  3 toggle @Override
231    public List<DocumentReference> getBackLinkedReferences(DocumentReference documentReference)
232    {
233  3 XWikiContext xcontext = this.xcontextProvider.get();
234  3 try {
235  3 return xcontext.getWiki().getDocument(documentReference, xcontext).getBackLinkedReferences(xcontext);
236    } catch (XWikiException e) {
237  0 this.logger.error("Failed to retrieve the back-links for document [{}].", documentReference, e);
238  0 return Collections.emptyList();
239    }
240    }
241   
 
242  10 toggle @Override
243    public List<DocumentReference> getDocumentReferences(SpaceReference spaceReference)
244    {
245  10 try {
246    // At the moment we don't have a way to retrieve only the direct children so we select all the descendants.
247    // This means we select all the documents from the specified space and from all the nested spaces.
248  10 String statement =
249    "select distinct(doc.fullName) from XWikiDocument as doc "
250    + "where doc.space = :space or doc.space like :spacePrefix escape '/'";
251  10 Query query = this.queryManager.createQuery(statement, Query.HQL);
252  10 query.setWiki(spaceReference.getWikiReference().getName());
253  10 String localSpaceReference = this.localEntityReferenceSerializer.serialize(spaceReference);
254  10 query.bindValue("space", localSpaceReference);
255  10 String spacePrefix = LIKE_SPECIAL_CHARS.matcher(localSpaceReference).replaceAll("/$1");
256  10 query.bindValue("spacePrefix", spacePrefix + ".%");
257   
258  10 List<DocumentReference> descendants = new ArrayList<>();
259  10 for (Object fullName : query.execute()) {
260  13 descendants.add(this.explicitDocumentReferenceResolver.resolve((String) fullName, spaceReference));
261    }
262  10 return descendants;
263    } catch (Exception e) {
264  0 this.logger.error("Failed to retrieve the documents from [{}].", spaceReference, e);
265  0 return Collections.emptyList();
266    }
267    }
268   
 
269  5 toggle @Override
270    public boolean updateParentField(final DocumentReference oldParentReference,
271    final DocumentReference newParentReference)
272    {
273  5 XWikiContext context = xcontextProvider.get();
274  5 XWiki wiki = context.getWiki();
275   
276  5 boolean popLevelProgress = false;
277  5 try {
278    // Note: This operation could have been done in Hibernate (using the Store API) in one single update query.
279    // However, due to XWiki's document cache, it`s better in the end to use the Document API and update each
280    // child document individually.
281  5 XWikiDocument oldParentDocument = wiki.getDocument(oldParentReference, context);
282   
283  5 List<DocumentReference> childReferences = oldParentDocument.getChildrenReferences(context);
284   
285  5 if (childReferences.size() > 0) {
286  1 this.progressManager.pushLevelProgress(childReferences.size(), this);
287  1 popLevelProgress = true;
288    }
289   
290  5 for (DocumentReference childReference : childReferences) {
291  2 this.progressManager.startStep(this);
292   
293  2 XWikiDocument childDocument = wiki.getDocument(childReference, context);
294  2 childDocument.setParentReference(newParentReference);
295   
296  2 wiki.saveDocument(childDocument, "Updated parent field.", true, context);
297    }
298   
299  5 if (childReferences.size() > 0) {
300  1 this.logger.info("Document parent fields updated from [{}] to [{}] for [{}] documents.",
301    oldParentReference, newParentReference, childReferences.size());
302    }
303    } catch (Exception e) {
304  0 this.logger.error("Failed to update the document parent fields from [{}] to [{}].", oldParentReference,
305    newParentReference, e);
306  0 return false;
307    } finally {
308  5 if (popLevelProgress) {
309  1 this.progressManager.popLevelProgress(this);
310    }
311    }
312   
313  5 return true;
314    }
315   
 
316  39 toggle @Override
317    public DocumentReference setContextUserReference(DocumentReference userReference)
318    {
319  39 XWikiContext context = xcontextProvider.get();
320  39 DocumentReference previousUserReference = context.getUserReference();
321  39 context.setUserReference(userReference);
322  39 return previousUserReference;
323    }
324   
 
325  8 toggle @Override
326    public void update(DocumentReference documentReference, Map<String, String> parameters)
327    {
328  8 try {
329  8 XWikiContext context = xcontextProvider.get();
330  8 XWiki wiki = context.getWiki();
331  8 XWikiDocument document = wiki.getDocument(documentReference, context);
332  8 boolean save = false;
333   
334  8 String title = parameters.get("title");
335  8 if (title != null && !title.equals(document.getTitle())) {
336  1 document.setTitle(title);
337  1 save = true;
338    }
339   
340    // Some old applications still rely on the parent/child links between documents.
341    // For the retro-compatibility, we synchronize the "parent" field of the document with the (real)
342    // hierarchical parent.
343    //
344    // But if the user has voluntary enabled the legacy "parent/child" mechanism for the breadcrumbs, we keep
345    // the old behaviour when location and parent/child mechanism were not linked.
346    //
347    // More information: http://jira.xwiki.org/browse/XWIKI-13493
348  8 if (!parentChildConfiguration.isParentChildMechanismEnabled()) {
349  7 DocumentReference hierarchicalParent = getHierarchicalParent(documentReference);
350  7 if (!hierarchicalParent.equals(document.getParentReference())) {
351  7 document.setParentReference(hierarchicalParent);
352  7 save = true;
353    }
354    }
355   
356  8 if (save) {
357  7 wiki.saveDocument(document, "Update document after refactoring.", true, context);
358  7 this.logger.info("Document [{}] has been updated.", documentReference);
359    }
360    } catch (Exception e) {
361  0 this.logger.error("Failed to update the document [{}] after refactoring.", documentReference, e);
362    }
363    }
364   
 
365  7 toggle private DocumentReference getHierarchicalParent(DocumentReference documentReference)
366    {
367  7 final String spaceHomePage = entityReferenceProvider.getDefaultReference(EntityType.DOCUMENT).getName();
368   
369  7 EntityReference parentOfTheSpace = documentReference.getLastSpaceReference().getParent();
370   
371  7 boolean pageIsNotTerminal = documentReference.getName().equals(spaceHomePage);
372   
373    // Case 1: The document has the location A.B.C.WebHome
374    // The parent should be A.B.WebHome
375  7 if (pageIsNotTerminal && parentOfTheSpace.getType() == EntityType.SPACE) {
376  3 return new DocumentReference(spaceHomePage, new SpaceReference(parentOfTheSpace));
377    }
378   
379    // Case 2: The document has the location A.WebHome
380    // The parent should be Main.WebHome
381  4 if (pageIsNotTerminal && parentOfTheSpace.getType() == EntityType.WIKI) {
382  1 return new DocumentReference(spaceHomePage, new SpaceReference(
383    entityReferenceProvider.getDefaultReference(EntityType.SPACE).getName(),
384    documentReference.getWikiReference()));
385    }
386   
387    // Case 3: The document has the location A.B
388    // The parent should be A.WebHome
389  3 return new DocumentReference(spaceHomePage, documentReference.getLastSpaceReference());
390    }
391    }