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

File JarExtensionJobFinishingListener.java

 

Coverage histogram

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

Code metrics

54
103
15
2
362
259
48
0.47
6.87
7.5
3.2

Classes

Class Line # Actions
JarExtensionJobFinishingListener 65 97 0% 44 20
0.874213887.4%
JarExtensionJobFinishingListener.UninstalledExtensionCollection 67 6 0% 4 0
1.0100%
 

Contributing tests

This file is covered by 23 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   
21    package org.xwiki.extension.jar.internal.handler;
22   
23    import java.util.Arrays;
24    import java.util.Collection;
25    import java.util.HashMap;
26    import java.util.HashSet;
27    import java.util.List;
28    import java.util.Map;
29    import java.util.Set;
30    import java.util.Stack;
31   
32    import javax.inject.Inject;
33    import javax.inject.Named;
34    import javax.inject.Singleton;
35   
36    import org.slf4j.Logger;
37    import org.xwiki.classloader.ClassLoaderManager;
38    import org.xwiki.component.annotation.Component;
39    import org.xwiki.context.Execution;
40    import org.xwiki.context.ExecutionContext;
41    import org.xwiki.extension.ExtensionId;
42    import org.xwiki.extension.InstalledExtension;
43    import org.xwiki.extension.ResolveException;
44    import org.xwiki.extension.UninstallException;
45    import org.xwiki.extension.event.ExtensionEvent;
46    import org.xwiki.extension.event.ExtensionUninstalledEvent;
47    import org.xwiki.extension.event.ExtensionUpgradedEvent;
48    import org.xwiki.extension.handler.ExtensionHandler;
49    import org.xwiki.extension.handler.ExtensionInitializer;
50    import org.xwiki.extension.repository.InstalledExtensionRepository;
51    import org.xwiki.job.event.JobFinishingEvent;
52    import org.xwiki.job.event.JobStartedEvent;
53    import org.xwiki.observation.EventListener;
54    import org.xwiki.observation.event.Event;
55   
56    /**
57    * Listen to job finishing events to properly refresh extension ClassLoader when an uninstall job has been executed.
58    *
59    * @version $Id: 0ed83d548953d3566bfe47d6156a5381904c9d84 $
60    * @since 4.0M1
61    */
62    @Component
63    @Singleton
64    @Named("JarExtensionJobFinishingListener")
 
65    public class JarExtensionJobFinishingListener implements EventListener
66    {
 
67    private static final class UninstalledExtensionCollection
68    {
69    private boolean rootNamespace;
70   
71    private Set<String> namespaces;
72   
 
73  39 toggle private void add(String namespace)
74    {
75  39 if (!this.rootNamespace) {
76  38 if (namespace != null) {
77  29 if (this.namespaces == null) {
78  21 this.namespaces = new HashSet<String>();
79    }
80  29 this.namespaces.add(namespace);
81    } else {
82  9 this.rootNamespace = true;
83    }
84    }
85    }
86    }
87   
88    /** The list of events observed. */
89    private static final List<Event> EVENTS = Arrays.asList(new ExtensionUninstalledEvent(),
90    new ExtensionUpgradedEvent(), new JobStartedEvent(), new JobFinishingEvent());
91   
92    /**
93    * Jar extension ClassLoader that will be properly refreshed.
94    */
95    @Inject
96    private ClassLoaderManager jarExtensionClassLoader;
97   
98    /**
99    * Extension initializer used to reinstall extensions in a new ClassLoader.
100    */
101    @Inject
102    private ExtensionInitializer extensionInitializer;
103   
104    /**
105    * The local extension repository from which extension are initialized.
106    */
107    @Inject
108    private InstalledExtensionRepository installedExtensionRepository;
109   
110    @Inject
111    @Named(JarExtensionHandler.JAR)
112    private ExtensionHandler jarHandler;
113   
114    @Inject
115    private Execution execution;
116   
117    /**
118    * The logger to log.
119    */
120    @Inject
121    private Logger logger;
122   
 
123  157 toggle @Override
124    public String getName()
125    {
126  157 return "JarExtensionJobFinishedListener";
127    }
128   
 
129  25 toggle @Override
130    public List<Event> getEvents()
131    {
132  25 return EVENTS;
133    }
134   
 
135  194 toggle private void pushUninstallLevel()
136    {
137  194 ExecutionContext context = this.execution.getContext();
138   
139  194 if (context != null) {
140  194 Stack<UninstalledExtensionCollection> extensions = getUninstalledExtensionCollectionStack(true);
141   
142  194 extensions.push(null);
143    }
144    }
145   
 
146  194 toggle private void popUninstallLevel()
147    {
148  194 ExecutionContext context = this.execution.getContext();
149   
150  194 if (context != null) {
151  194 Stack<UninstalledExtensionCollection> extensions = getUninstalledExtensionCollectionStack(false);
152   
153  194 if (extensions != null) {
154  194 extensions.pop();
155    }
156    }
157    }
158   
 
159  621 toggle private Stack<UninstalledExtensionCollection> getUninstalledExtensionCollectionStack(boolean create)
160    {
161  621 ExecutionContext context = this.execution.getContext();
162  621 final String contextKey = "extension.jar.uninstalledExtensions";
163   
164  621 if (context != null) {
165  621 @SuppressWarnings("unchecked")
166    Stack<UninstalledExtensionCollection> extensions =
167    (Stack<UninstalledExtensionCollection>) context.getProperty(contextKey);
168   
169  621 if (extensions == null && create) {
170  104 extensions = new Stack<UninstalledExtensionCollection>();
171  104 context.setProperty(contextKey, extensions);
172    }
173   
174  621 return extensions;
175    }
176   
177  0 return null;
178    }
179   
 
180  233 toggle private UninstalledExtensionCollection getCurrentJobUninstalledExtensions(boolean create)
181    {
182  233 ExecutionContext context = this.execution.getContext();
183   
184  233 if (context != null) {
185  233 Stack<UninstalledExtensionCollection> extensions = getUninstalledExtensionCollectionStack(false);
186   
187  233 if (extensions != null) {
188  233 UninstalledExtensionCollection collection = extensions.peek();
189   
190  233 if (collection == null && create) {
191  28 collection = new UninstalledExtensionCollection();
192  28 extensions.set(extensions.size() - 1, collection);
193    }
194   
195  233 return collection;
196    }
197    }
198   
199  0 return null;
200    }
201   
 
202  39 toggle private void addUninstalledExtension(ExtensionId id, String namespace)
203    {
204  39 UninstalledExtensionCollection collection = getCurrentJobUninstalledExtensions(true);
205   
206  39 if (collection != null) {
207  39 collection.add(namespace);
208    }
209    }
210   
 
211  441 toggle @Override
212    public void onEvent(Event event, Object source, Object data)
213    {
214  441 if (event instanceof ExtensionUninstalledEvent) {
215  48 onExtensionRemovedEvent((ExtensionUninstalledEvent) event, (InstalledExtension) source);
216  393 } else if (event instanceof ExtensionUpgradedEvent) {
217  5 for (InstalledExtension previous : (Collection<InstalledExtension>) data) {
218  5 onExtensionRemovedEvent((ExtensionUpgradedEvent) event, previous);
219    }
220  388 } else if (event instanceof JobStartedEvent) {
221  194 onJobStartedEvent();
222    } else {
223  194 onJobFinishedEvent();
224    }
225    }
226   
 
227  53 toggle private void onExtensionRemovedEvent(ExtensionEvent event, InstalledExtension extension)
228    {
229  53 if (JarExtensionHandler.isSupported(extension.getType())) {
230  39 addUninstalledExtension(event.getExtensionId(), event.getNamespace());
231    }
232    }
233   
 
234  194 toggle private void onJobStartedEvent()
235    {
236  194 pushUninstallLevel();
237    }
238   
 
239  194 toggle private void onJobFinishedEvent()
240    {
241  194 UninstalledExtensionCollection collection = getCurrentJobUninstalledExtensions(false);
242   
243  194 popUninstallLevel();
244   
245  194 if (collection != null) {
246  28 if (collection.rootNamespace) {
247    // Unload extensions
248  9 unloadJARsFromNamespace(null, null);
249   
250    // Drop class loaders
251  9 this.jarExtensionClassLoader.dropURLClassLoaders();
252   
253    // Load JAR extensions
254  9 this.extensionInitializer.initialize(null, JarExtensionHandler.JAR);
255    // Load WEBJAR extensions
256  9 this.extensionInitializer.initialize(null, JarExtensionHandler.WEBJAR);
257  19 } else if (collection.namespaces != null) {
258  19 for (String namespace : collection.namespaces) {
259    // Unload extensions
260  21 unloadJARsFromNamespace(namespace, null);
261   
262    // Drop class loader
263  21 this.jarExtensionClassLoader.dropURLClassLoader(namespace);
264   
265    // Load JAR extensions
266  21 this.extensionInitializer.initialize(namespace, JarExtensionHandler.JAR);
267    // Load WEBJAR extensions
268  21 this.extensionInitializer.initialize(namespace, JarExtensionHandler.WEBJAR);
269    }
270    }
271    }
272    }
273   
 
274  30 toggle private void unloadJARsFromNamespace(String namespace, Map<String, Set<InstalledExtension>> unloadedExtensions)
275    {
276  30 Map<String, Set<InstalledExtension>> unloadedExtensionsMap = unloadedExtensions;
277  30 if (unloadedExtensionsMap == null) {
278  30 unloadedExtensionsMap = new HashMap<>();
279    }
280   
281    // Load extensions from local repository
282  30 Collection<InstalledExtension> installedExtensions;
283  30 if (namespace != null) {
284  21 installedExtensions = this.installedExtensionRepository.getInstalledExtensions(namespace);
285    } else {
286  9 installedExtensions = this.installedExtensionRepository.getInstalledExtensions();
287    }
288   
289  30 for (InstalledExtension installedExtension : installedExtensions) {
290  95 if (JarExtensionHandler.isSupported(installedExtension.getType())) {
291  95 if (namespace == null || !installedExtension.isInstalled(null)) {
292  50 try {
293  50 unloadJAR(installedExtension, namespace, unloadedExtensionsMap);
294    } catch (Exception e) {
295  0 this.logger.error("Failed to unload installed extension [{}]", installedExtension, e);
296    }
297    }
298    }
299    }
300    }
301   
 
302  68 toggle private void unloadJAR(InstalledExtension installedExtension, String namespace,
303    Map<String, Set<InstalledExtension>> unloadedExtensions) throws UninstallException
304    {
305  68 Set<InstalledExtension> unloadedExtensionsInNamespace = unloadedExtensions.get(namespace);
306   
307  68 if (unloadedExtensionsInNamespace == null) {
308  39 unloadedExtensionsInNamespace = new HashSet<InstalledExtension>();
309  39 unloadedExtensions.put(namespace, unloadedExtensionsInNamespace);
310    }
311   
312  68 if (unloadedExtensionsInNamespace.contains(installedExtension)) {
313  0 return;
314    }
315  68 if (namespace == null) {
316  35 if (installedExtension.isInstalled(null)) {
317  17 try {
318  17 Map<String, Collection<InstalledExtension>> bDependencies =
319    this.installedExtensionRepository.getBackwardDependencies(installedExtension.getId());
320   
321  17 for (Map.Entry<String, Collection<InstalledExtension>> entry : bDependencies.entrySet()) {
322  0 for (InstalledExtension bDependency : entry.getValue()) {
323  0 unloadJAR(bDependency, entry.getKey(), unloadedExtensions);
324    }
325    }
326    } catch (ResolveException e) {
327  0 this.logger.error("Failed to get backward dependencies for installed extension [{}]",
328    installedExtension, e);
329    }
330   
331  17 this.jarHandler.uninstall(installedExtension, null, null);
332  17 unloadedExtensionsInNamespace.add(installedExtension);
333    } else {
334  18 for (String namespace2 : installedExtension.getNamespaces()) {
335  18 unloadJAR(installedExtension, namespace2, unloadedExtensions);
336    }
337    }
338    } else {
339  33 unloadJARFromNamespace(installedExtension, namespace, unloadedExtensions, unloadedExtensionsInNamespace);
340    }
341    }
342   
 
343  33 toggle private void unloadJARFromNamespace(InstalledExtension installedExtension, String namespace,
344    Map<String, Set<InstalledExtension>> unloadedExtensions, Set<InstalledExtension> unloadedExtensionsInNamespace)
345    throws UninstallException
346    {
347  33 try {
348  33 Collection<InstalledExtension> bDependencies = this.installedExtensionRepository
349    .getBackwardDependencies(installedExtension.getId().getId(), namespace);
350   
351  33 for (InstalledExtension bDependency : bDependencies) {
352  0 unloadJAR(bDependency, namespace, unloadedExtensions);
353    }
354    } catch (ResolveException e) {
355  0 this.logger.error("Failed to get backward dependencies for installed extension [{}] on namespace [{}]",
356    installedExtension, namespace, e);
357    }
358   
359  33 this.jarHandler.uninstall(installedExtension, namespace, null);
360  33 unloadedExtensionsInNamespace.add(installedExtension);
361    }
362    }