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

File DefaultLinkCheckerThread.java

 

Coverage histogram

../../../../../../img/srcFileCovDistChart10.png
0% of files have more coverage

Code metrics

18
53
8
1
239
142
23
0.43
6.62
8
2.88

Classes

Class Line # Actions
DefaultLinkCheckerThread 55 53 0% 23 0
1.0100%
 

Contributing tests

This file is covered by 9 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.rendering.internal.transformation.linkchecker;
21   
22    import java.util.HashMap;
23    import java.util.List;
24    import java.util.Map;
25    import java.util.Queue;
26    import java.util.concurrent.ConcurrentHashMap;
27    import java.util.regex.Matcher;
28    import java.util.regex.Pattern;
29   
30    import javax.inject.Inject;
31    import javax.inject.Provider;
32   
33    import org.slf4j.Logger;
34    import org.xwiki.component.annotation.Component;
35    import org.xwiki.component.annotation.InstantiationStrategy;
36    import org.xwiki.component.descriptor.ComponentInstantiationStrategy;
37    import org.xwiki.observation.ObservationManager;
38    import org.xwiki.rendering.transformation.linkchecker.InvalidURLEvent;
39    import org.xwiki.rendering.transformation.linkchecker.LinkCheckerThreadInitializer;
40    import org.xwiki.rendering.transformation.linkchecker.LinkCheckerTransformationConfiguration;
41    import org.xwiki.rendering.transformation.linkchecker.LinkState;
42    import org.xwiki.rendering.transformation.linkchecker.LinkStateManager;
43   
44    /**
45    * Thread that regularly check for Links to be checked on a Queue, and for each link tries to connect to it and save the
46    * result in the {@link LinkStateManager}. In order to have good performance we only recheck a link if it's not been
47    * checked for a certain time.
48    *
49    * @version $Id: bc0971b6d15108459f58b4b592ebd05ba08065d9 $
50    * @since 5.3RC1
51    */
52    // TODO: If the LinkCheckerTransformation component is unregistered, then stop the thread.
53    @Component
54    @InstantiationStrategy(ComponentInstantiationStrategy.PER_LOOKUP)
 
55    public class DefaultLinkCheckerThread extends java.lang.Thread implements LinkCheckerThread
56    {
57    private static final String EVENT_DATA_SOURCE = "source";
58    private static final String EVENT_DATA_URL = "url";
59    private static final String EVENT_DATA_STATE = "state";
60    private static final String EVENT_DATA_CONTEXTDATA = "contextData";
61   
62    @Inject
63    private Logger logger;
64   
65    @Inject
66    private Provider<List<LinkCheckerThreadInitializer>> linkCheckerThreadInitializersProvider;
67   
68    /**
69    * The state manager containing the state of all checked links.
70    */
71    @Inject
72    private LinkStateManager linkStateManager;
73   
74    @Inject
75    private Provider<ObservationManager> observationManagerProvider;
76   
77    /**
78    * The HTTP checker used to connect to links to verify their validity.
79    */
80    @Inject
81    private HTTPChecker httpChecker;
82   
83    @Inject
84    private LinkCheckerTransformationConfiguration configuration;
85   
86    /**
87    * The queue containing links to check.
88    */
89    private Queue<LinkQueueItem> linkQueue;
90   
91    /**
92    * Allows to stop this thread, used in {@link #stopProcessing()}.
93    */
94    private volatile boolean shouldStop;
95   
 
96  7 toggle @Override
97    public void startProcessing(Queue<LinkQueueItem> linkQueue)
98    {
99  7 this.linkQueue = linkQueue;
100  7 start();
101    }
102   
 
103  1 toggle @Override
104    public void run(Queue<LinkQueueItem> linkQueue)
105    {
106  1 this.linkQueue = linkQueue;
107  1 run();
108    }
109   
 
110  8 toggle @Override
111    public void run()
112    {
113    // Allow external code to perform initialization of this thread.
114    // This is useful for example if external code needs to initialize the Execution Context.
115  8 for (LinkCheckerThreadInitializer initializer : this.linkCheckerThreadInitializersProvider.get()) {
116  2 initializer.initialize();
117    }
118   
119  64 while (!this.shouldStop) {
120  63 try {
121  63 processLinkQueue();
122  62 Thread.sleep(300L);
123    } catch (Exception e) {
124    // There was an unexpected problem, we stop this checker thread and log the problem.
125  7 this.logger.error("Link checker Thread was stopped due to some problem", e);
126  7 break;
127    }
128    }
129    }
130   
131    /**
132    * Stop the thread.
133    */
 
134  6 toggle @Override
135    public void stopProcessing()
136    {
137  6 this.shouldStop = true;
138    // Make sure the Thread goes out of sleep if it's sleeping so that it stops immediately.
139  6 interrupt();
140    }
141   
142    /**
143    * Read the queue and find links to process, removing links that have already been checked out recently.
144    */
 
145  66 toggle protected void processLinkQueue()
146    {
147  66 long timeout = this.configuration.getCheckTimeout();
148  65 List<Pattern> excludedReferencePatterns = this.configuration.getExcludedReferencePatterns();
149   
150    // Unqueue till we find an item that needs to be processed. We process an item if:
151    // - it isn't present in the state map
152    // - it is present but not enough time has elapsed since its last check time
153  65 LinkQueueItem queueItem = null;
154  65 boolean shouldBeChecked = false;
155   
156  77 while (!this.linkQueue.isEmpty() && !shouldBeChecked) {
157  15 queueItem = this.linkQueue.poll();
158   
159    // Don't check excluded references
160  15 shouldBeChecked = isExcluded(queueItem.getContentReference(), excludedReferencePatterns);
161  15 if (!shouldBeChecked) {
162  3 break;
163    }
164   
165  12 Map<String, LinkState> contentReferences =
166    this.linkStateManager.getLinkStates().get(queueItem.getLinkReference());
167  12 if (contentReferences != null) {
168  2 LinkState state = contentReferences.get(queueItem.getContentReference());
169  2 if (state != null && (System.currentTimeMillis() - state.getLastCheckedTime() <= timeout)) {
170  1 shouldBeChecked = false;
171    }
172    }
173    }
174   
175  65 if (shouldBeChecked && queueItem != null) {
176  11 checkLink(queueItem);
177    }
178    }
179   
 
180  15 toggle private boolean isExcluded(String contentReference, List<Pattern> excludedReferencePatterns)
181    {
182  15 for (Pattern pattern : excludedReferencePatterns) {
183  5 Matcher matcher = pattern.matcher(contentReference);
184  5 if (matcher.matches()) {
185  3 return false;
186    }
187    }
188   
189  12 return true;
190    }
191   
192    /**
193    * Perform the HTTP connection and save the result in the {@link LinkStateManager}.
194    *
195    * @param queueItem the link to check
196    */
 
197  11 toggle private void checkLink(LinkQueueItem queueItem)
198    {
199  11 int responseCode = this.httpChecker.check(queueItem.getLinkReference());
200   
201  11 Map<String, LinkState> contentReferences =
202    this.linkStateManager.getLinkStates().get(queueItem.getLinkReference());
203  11 if (contentReferences == null) {
204  10 contentReferences = new ConcurrentHashMap<>();
205    }
206  11 LinkState state = new LinkState(responseCode, System.currentTimeMillis(), queueItem.getContextData());
207  11 contentReferences.put(queueItem.getContentReference(), state);
208  11 this.linkStateManager.getLinkStates().put(queueItem.getLinkReference(), contentReferences);
209   
210    // If there's an error, then send an Observation Event so that anyone interested can listen to it.
211  11 if (responseCode < 200 || responseCode > 299) {
212  4 Map<String, Object> eventData = new HashMap<>();
213  4 eventData.put(EVENT_DATA_URL, queueItem.getLinkReference());
214  4 eventData.put(EVENT_DATA_SOURCE, queueItem.getContentReference());
215  4 eventData.put(EVENT_DATA_STATE, state);
216  4 eventData.put(EVENT_DATA_CONTEXTDATA, queueItem.getContextData());
217  4 sendEvent(queueItem.getLinkReference(), eventData);
218    }
219    }
220   
221    /**
222    * Send an {@link InvalidURLEvent} event.
223    *
224    * @param url the failing URL
225    * @param data the Map containing data (link url, link source reference, state object)
226    */
 
227  4 toggle private void sendEvent(String url, Map<String, Object> data)
228    {
229    // Dynamically look for an Observation Manager and only send the event if one can be found.
230  4 try {
231  4 ObservationManager observationManager = this.observationManagerProvider.get();
232  2 observationManager.notify(new InvalidURLEvent(url), data);
233    } catch (Exception e) {
234    // Failed to find an Observation Manager, continnue, but log a warning since it's not really normal
235  2 this.logger.warn("The Invalid URL Event for URL [{}] (source [{}]) wasn't sent as no Observation Manager "
236    + "Component was found", url, data.get(EVENT_DATA_SOURCE));
237    }
238    }
239    }