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

File XarExtensionJobFinishedListener.java

 

Coverage histogram

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

Code metrics

48
121
12
1
423
301
46
0.38
10.08
12
3.83

Classes

Class Line # Actions
XarExtensionJobFinishedListener 77 121 0% 46 30
0.8342541583.4%
 

Contributing tests

This file is covered by 25 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.extension.xar.internal.handler;
21   
22    import java.io.IOException;
23    import java.util.Arrays;
24    import java.util.Collection;
25    import java.util.Collections;
26    import java.util.HashSet;
27    import java.util.List;
28    import java.util.Map;
29    import java.util.Set;
30   
31    import javax.inject.Inject;
32    import javax.inject.Named;
33    import javax.inject.Provider;
34    import javax.inject.Singleton;
35   
36    import org.slf4j.Logger;
37    import org.xwiki.component.annotation.Component;
38    import org.xwiki.context.Execution;
39    import org.xwiki.context.ExecutionContext;
40    import org.xwiki.extension.LocalExtension;
41    import org.xwiki.extension.internal.validator.AbstractExtensionValidator;
42    import org.xwiki.extension.job.internal.InstallJob;
43    import org.xwiki.extension.job.internal.UninstallJob;
44    import org.xwiki.extension.repository.InstalledExtensionRepository;
45    import org.xwiki.extension.xar.internal.handler.packager.PackageConfiguration;
46    import org.xwiki.extension.xar.internal.handler.packager.Packager;
47    import org.xwiki.extension.xar.internal.repository.XarInstalledExtensionRepository;
48    import org.xwiki.extension.xar.question.CleanPagesQuestion;
49    import org.xwiki.job.Job;
50    import org.xwiki.job.Request;
51    import org.xwiki.job.event.JobFinishingEvent;
52    import org.xwiki.model.reference.DocumentReference;
53    import org.xwiki.model.reference.LocalDocumentReference;
54    import org.xwiki.model.reference.WikiReference;
55    import org.xwiki.observation.EventListener;
56    import org.xwiki.observation.ObservationManager;
57    import org.xwiki.observation.event.Event;
58    import org.xwiki.security.SecurityReference;
59    import org.xwiki.security.SecurityReferenceFactory;
60    import org.xwiki.security.authorization.cache.SecurityCache;
61    import org.xwiki.wiki.descriptor.WikiDescriptorManager;
62    import org.xwiki.wiki.manager.WikiManagerException;
63    import org.xwiki.xar.XarEntry;
64   
65    import com.xpn.xwiki.XWikiContext;
66    import com.xpn.xwiki.doc.XWikiDocument;
67   
68    /**
69    * Listen to job finished events to properly clean pages after upgrades.
70    *
71    * @version $Id: 39f8653148be45d0ddd1458f6d18401317f2b919 $
72    * @since 4.3M1
73    */
74    @Component
75    @Singleton
76    @Named("XarExtensionJobFinishedListener")
 
77    public class XarExtensionJobFinishedListener implements EventListener
78    {
79    /**
80    * The list of events observed.
81    */
82    private static final List<Event> EVENTS =
83    Arrays.<Event>asList(new JobFinishingEvent(InstallJob.JOBTYPE), new JobFinishingEvent(UninstallJob.JOBTYPE));
84   
85    @Inject
86    private Execution execution;
87   
88    @Inject
89    private Provider<Packager> packagerProvider;
90   
91    @Inject
92    private Provider<XWikiContext> xcontextProvider;
93   
94    @Inject
95    private Logger logger;
96   
97    @Inject
98    @Named(XarExtensionHandler.TYPE)
99    private InstalledExtensionRepository xarRepository;
100   
101    @Inject
102    private WikiDescriptorManager wikiManager;
103   
104    @Inject
105    private SecurityCache security;
106   
107    @Inject
108    private SecurityReferenceFactory securityFactory;
109   
110    @Inject
111    private ObservationManager observation;
112   
 
113  404 toggle @Override
114    public String getName()
115    {
116  404 return "XarExtensionJobFinishedListener";
117    }
118   
 
119  101 toggle @Override
120    public List<Event> getEvents()
121    {
122  101 return EVENTS;
123    }
124   
 
125  71 toggle @Override
126    public void onEvent(Event event, Object source, Object data)
127    {
128  71 JobFinishingEvent jobFinishingEvent = (JobFinishingEvent) event;
129   
130  71 if (!jobFinishingEvent.getRequest().isRemote()) {
131  69 ExecutionContext context = this.execution.getContext();
132   
133  69 if (context != null) {
134  69 XarExtensionPlan xarExtensionPlan =
135    (XarExtensionPlan) context.getProperty(XarExtensionPlan.CONTEXTKEY_XARINSTALLPLAN);
136   
137  69 if (xarExtensionPlan != null) {
138  58 Map<String, Map<XarEntry, XarExtensionPlanEntry>> previousXAREntries =
139    xarExtensionPlan.previousXAREntries;
140  58 Map<String, Map<XarEntry, LocalExtension>> nextXAREntries = xarExtensionPlan.nextXAREntries;
141   
142  58 Map<XarEntry, XarExtensionPlanEntry> rootPreviousPages = previousXAREntries.get(null);
143  58 if (rootPreviousPages == null) {
144  56 rootPreviousPages = Collections.emptyMap();
145    }
146  58 Map<XarEntry, LocalExtension> rootNextPages = nextXAREntries.get(null);
147  58 if (rootNextPages == null) {
148  47 rootNextPages = Collections.emptyMap();
149    }
150   
151    ////////////////////
152    // Delete pages
153    ////////////////////
154   
155  58 maybeDeletePages(jobFinishingEvent, xarExtensionPlan, previousXAREntries, (Job) source, context);
156   
157    ////////////////////////////////////////
158    // Invalidate security cache
159    ////////////////////////////////////////
160   
161  58 invalidateSecurityCache(previousXAREntries, rootPreviousPages, nextXAREntries, rootNextPages);
162    }
163    }
164    }
165    }
166   
 
167  58 toggle private void maybeDeletePages(JobFinishingEvent jobFinishingEvent, XarExtensionPlan xarExtensionPlan,
168    Map<String, Map<XarEntry, XarExtensionPlanEntry>> previousXAREntries, Job job, ExecutionContext context)
169    {
170  58 try {
171  58 XWikiContext xcontext = this.xcontextProvider.get();
172   
173  58 Packager packager = this.packagerProvider.get();
174   
175    // Get pages to delete
176   
177  58 Set<DocumentReference> pagesToDelete = new HashSet<>();
178   
179  58 for (Map.Entry<String, Map<XarEntry, XarExtensionPlanEntry>> previousWikiEntry : previousXAREntries
180    .entrySet()) {
181  24 if (!previousWikiEntry.getValue().isEmpty()) {
182  24 try {
183  24 List<DocumentReference> references =
184    packager.getDocumentReferences(previousWikiEntry.getValue().keySet(),
185    createPackageConfiguration(jobFinishingEvent.getRequest(), previousWikiEntry.getKey()));
186   
187  24 for (DocumentReference reference : references) {
188    // Ignore document that are part of other installed extensions (don't even
189    // propose to enable them)
190  1718 if (((XarInstalledExtensionRepository) this.xarRepository)
191    .getXarInstalledExtensions(reference).isEmpty()) {
192  158 pagesToDelete.add(reference);
193    }
194    }
195    } catch (Exception e) {
196  0 this.logger.warn("Exception when cleaning pages removed since previous xar extension version",
197    e);
198    }
199    }
200    }
201   
202    // Create cleanup question
203   
204  58 CleanPagesQuestion question = new CleanPagesQuestion(pagesToDelete);
205   
206  58 Map<DocumentReference, Boolean> pages = question.getPages();
207   
208    // Remove pages which are in the next XAR packages
209  58 for (DocumentReference previousReference : pagesToDelete) {
210  158 if (xarExtensionPlan.containsNewPage(previousReference)) {
211  0 pages.remove(previousReference);
212    }
213    }
214   
215    // Deal with conflicts before sending the question
216   
217  58 for (Map.Entry<DocumentReference, Boolean> entry : pages.entrySet()) {
218  158 DocumentReference reference = entry.getKey();
219   
220    // Get current
221  158 XWikiDocument currentDocument;
222  158 try {
223  158 currentDocument = xcontext.getWiki().getDocument(reference, xcontext);
224    } catch (Exception e) {
225  0 this.logger.error("Failed to get document [{}]", reference, e);
226    // Lets be safe and skip removing that page
227  0 pages.put(reference, false);
228  0 continue;
229    }
230  158 if (currentDocument.isNew()) {
231    // Current already removed
232  0 pages.put(reference, false);
233  0 continue;
234    }
235   
236    // Get previous
237  158 XWikiDocument previousDocument;
238  158 try {
239  158 previousDocument = xarExtensionPlan.getPreviousXWikiDocument(reference, packager);
240    } catch (Exception e) {
241  0 this.logger.error("Failed to get previous version of document [{}]", reference, e);
242    // Lets be safe and skip removing that page
243  0 pages.put(reference, false);
244  0 continue;
245    }
246   
247    // Compare previous and current
248  158 try {
249  158 currentDocument.loadAttachmentsContentSafe(xcontext);
250  158 if (!currentDocument.equalsData(previousDocument)) {
251    // conflict between current and new
252  22 pages.put(reference, false);
253    }
254    } catch (Exception e) {
255  0 this.logger.error("Failed to load attachments", e);
256    // Lets be safe and skip removing that page
257  0 pages.put(reference, false);
258  0 continue;
259    }
260    }
261   
262    // Ask confirmation
263  58 if (!pages.isEmpty() && jobFinishingEvent.getRequest().isInteractive()) {
264  5 try {
265  5 job.getStatus().ask(question);
266    } catch (InterruptedException e) {
267  0 this.logger.warn("The thread has been interrupted", e);
268   
269    // The thread has been interrupted, do nothing
270  0 return;
271    }
272    }
273   
274    // Delete pages
275   
276  58 if (!pages.isEmpty()) {
277  19 deletePages(jobFinishingEvent, pages, packager);
278    }
279    } finally {
280    // Cleanup extension plan
281  58 try {
282  58 xarExtensionPlan.close();
283    } catch (IOException e) {
284  0 this.logger.error("Failed to close XAR extension plan", e);
285    }
286  58 context.setProperty(XarExtensionPlan.CONTEXTKEY_XARINSTALLPLAN, null);
287    }
288    }
289   
 
290  19 toggle private void deletePages(JobFinishingEvent jobFinishingEvent, Map<DocumentReference, Boolean> pages,
291    Packager packager)
292    {
293  19 PackageConfiguration configuration = createPackageConfiguration(jobFinishingEvent.getRequest());
294   
295  19 this.observation.notify(new DeletingXarExtensionPages(), this);
296   
297  19 try {
298  19 for (Map.Entry<DocumentReference, Boolean> entry : pages.entrySet()) {
299  158 if (entry.getValue()) {
300  136 packager.deleteDocument(entry.getKey(), configuration);
301    }
302    }
303    } finally {
304  19 this.observation.notify(new DeletedXarExtensionPages(), this);
305    }
306    }
307   
 
308  58 toggle private void invalidateSecurityCache(Map<String, Map<XarEntry, XarExtensionPlanEntry>> previousXAREntries,
309    Map<XarEntry, XarExtensionPlanEntry> previousRoot, Map<String, Map<XarEntry, LocalExtension>> nextXAREntries,
310    Map<XarEntry, LocalExtension> nextRoot)
311    {
312    // Extension installed on root
313  58 Collection<String> wikis;
314   
315  58 try {
316  58 wikis = this.wikiManager.getAllIds();
317    } catch (WikiManagerException e) {
318  0 this.logger.error("Failed to get wikis. Security cache won't be properly invalidated.", e);
319   
320  0 wikis = Collections.emptyList();
321    }
322   
323  58 for (String wiki : wikis) {
324  47 invalidateWikiSecurityCache(wiki, previousXAREntries.get(wiki), previousRoot, nextXAREntries.get(wiki),
325    nextRoot);
326    }
327    }
328   
 
329  47 toggle private void invalidateWikiSecurityCache(String wikiId, Map<XarEntry, XarExtensionPlanEntry> previous,
330    Map<XarEntry, XarExtensionPlanEntry> previousRoot, Map<XarEntry, LocalExtension> next,
331    Map<XarEntry, LocalExtension> nextRoot)
332    {
333  47 WikiReference wikiReference = new WikiReference(wikiId);
334   
335  47 if (previous != null) {
336  17 invalidateNextSecurityCache(wikiReference, previous, previousRoot, nextRoot);
337    }
338  47 if (previousRoot != null) {
339  47 invalidateNextSecurityCache(wikiReference, previousRoot, previousRoot, nextRoot);
340    }
341   
342  47 if (previous != null) {
343  17 invalidatePreviousSecurityCache(wikiReference, previous, next, nextRoot);
344    }
345  47 if (previousRoot != null) {
346  47 invalidatePreviousSecurityCache(wikiReference, previousRoot, next, nextRoot);
347    }
348    }
349   
 
350  64 toggle private void invalidateNextSecurityCache(WikiReference wikiReference, Map<XarEntry, XarExtensionPlanEntry> previous,
351    Map<XarEntry, XarExtensionPlanEntry> previousRoot, Map<XarEntry, LocalExtension> next)
352    {
353  64 if (previous == null) {
354  0 previous = previousRoot;
355    }
356   
357  64 for (XarEntry nextXarEntry : next.keySet()) {
358  195 if (previous != null) {
359  195 XarExtensionPlanEntry previousPlanEntry = previous.get(nextXarEntry);
360  195 if (previousPlanEntry == null) {
361  165 previousPlanEntry = previousRoot.get(nextXarEntry);
362    }
363   
364  195 if (previousPlanEntry != null) {
365  30 XarEntry previousXarEntry = previousPlanEntry.extension.getXarPackage().getEntry(nextXarEntry);
366   
367  30 if (previousXarEntry.getType() != nextXarEntry.getType()) {
368    // Different type
369  0 invalidateSecurityCache(nextXarEntry, wikiReference);
370    }
371    } else {
372    // New document
373  165 invalidateSecurityCache(nextXarEntry, wikiReference);
374    }
375    } else {
376    // New document
377  0 invalidateSecurityCache(nextXarEntry, wikiReference);
378    }
379    }
380    }
381   
 
382  64 toggle private void invalidatePreviousSecurityCache(WikiReference wikiReference,
383    Map<XarEntry, XarExtensionPlanEntry> previous, Map<XarEntry, LocalExtension> next,
384    Map<XarEntry, LocalExtension> nextRoot)
385    {
386  64 if (next == null) {
387  40 next = nextRoot;
388    }
389   
390  64 for (XarEntry nextXarEntry : previous.keySet()) {
391  1672 if (next == null || (!next.containsKey(nextXarEntry) && !nextRoot.containsKey(nextXarEntry))) {
392    // Deleted document
393  130 invalidateSecurityCache(nextXarEntry, wikiReference);
394    }
395    }
396    }
397   
 
398  295 toggle private void invalidateSecurityCache(LocalDocumentReference documentReference, WikiReference wikiReference)
399    {
400  295 SecurityReference securityReference =
401    this.securityFactory.newEntityReference(new DocumentReference(documentReference, wikiReference));
402  295 this.security.remove(securityReference);
403    }
404   
 
405  19 toggle private PackageConfiguration createPackageConfiguration(Request request)
406    {
407  19 return createPackageConfiguration(request, null);
408    }
409   
 
410  43 toggle private PackageConfiguration createPackageConfiguration(Request request, String wiki)
411    {
412  43 PackageConfiguration configuration = new PackageConfiguration();
413   
414  43 configuration.setInteractive(false);
415  43 configuration.setUser(
416    XarExtensionHandler.getRequestUserReference(AbstractExtensionValidator.PROPERTY_USERREFERENCE, request));
417  43 configuration.setWiki(wiki);
418  43 configuration.setVerbose(request.isVerbose());
419  43 configuration.setSkipMandatorytDocuments(true);
420   
421  43 return configuration;
422    }
423    }