1. Project Clover database Tue Dec 20 2016 21:24:09 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

20
54
8
1
241
144
23
0.43
6.75
8
2.88

Classes

Class Line # Actions
DefaultLinkCheckerThread 55 54 0% 23 1
0.987804998.8%
 

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: f276be5e8cddb943ce82613a19b27776f0205332 $
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  65 while (!this.shouldStop) {
120  64 try {
121  64 processLinkQueue();
122  63 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  67 toggle protected void processLinkQueue()
146    {
147  67 long timeout = this.configuration.getCheckTimeout();
148  66 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  66 LinkQueueItem queueItem = null;
154  66 boolean shouldBeChecked = false;
155   
156  78 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) {
170  2 if (System.currentTimeMillis() - state.getLastCheckedTime() <= timeout) {
171  1 shouldBeChecked = false;
172    }
173    }
174    }
175    }
176   
177  66 if (shouldBeChecked && queueItem != null) {
178  11 checkLink(queueItem);
179    }
180    }
181   
 
182  15 toggle private boolean isExcluded(String contentReference, List<Pattern> excludedReferencePatterns)
183    {
184  15 for (Pattern pattern : excludedReferencePatterns) {
185  5 Matcher matcher = pattern.matcher(contentReference);
186  5 if (matcher.matches()) {
187  3 return false;
188    }
189    }
190   
191  12 return true;
192    }
193   
194    /**
195    * Perform the HTTP connection and save the result in the {@link LinkStateManager}.
196    *
197    * @param queueItem the link to check
198    */
 
199  11 toggle private void checkLink(LinkQueueItem queueItem)
200    {
201  11 int responseCode = this.httpChecker.check(queueItem.getLinkReference());
202   
203  11 Map<String, LinkState> contentReferences =
204    this.linkStateManager.getLinkStates().get(queueItem.getLinkReference());
205  11 if (contentReferences == null) {
206  10 contentReferences = new ConcurrentHashMap<>();
207    }
208  11 LinkState state = new LinkState(responseCode, System.currentTimeMillis(), queueItem.getContextData());
209  11 contentReferences.put(queueItem.getContentReference(), state);
210  11 this.linkStateManager.getLinkStates().put(queueItem.getLinkReference(), contentReferences);
211   
212    // If there's an error, then send an Observation Event so that anyone interested can listen to it.
213  11 if (responseCode < 200 || responseCode > 299) {
214  4 Map<String, Object> eventData = new HashMap<>();
215  4 eventData.put(EVENT_DATA_URL, queueItem.getLinkReference());
216  4 eventData.put(EVENT_DATA_SOURCE, queueItem.getContentReference());
217  4 eventData.put(EVENT_DATA_STATE, state);
218  4 eventData.put(EVENT_DATA_CONTEXTDATA, queueItem.getContextData());
219  4 sendEvent(queueItem.getLinkReference(), eventData);
220    }
221    }
222   
223    /**
224    * Send an {@link InvalidURLEvent} event.
225    *
226    * @param url the failing URL
227    * @param data the Map containing data (link url, link source reference, state object)
228    */
 
229  4 toggle private void sendEvent(String url, Map<String, Object> data)
230    {
231    // Dynamically look for an Observation Manager and only send the event if one can be found.
232  4 try {
233  4 ObservationManager observationManager = this.observationManagerProvider.get();
234  2 observationManager.notify(new InvalidURLEvent(url), data);
235    } catch (Exception e) {
236    // Failed to find an Observation Manager, continnue, but log a warning since it's not really normal
237  2 this.logger.warn("The Invalid URL Event for URL [{}] (source [{}]) wasn't sent as no Observation Manager "
238    + "Component was found", url, data.get(EVENT_DATA_SOURCE));
239    }
240    }
241    }