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

File LinkCheckerTransformation.java

 

Coverage histogram

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

Code metrics

10
26
6
1
182
93
11
0.42
4.33
6
1.83

Classes

Class Line # Actions
LinkCheckerTransformation 58 26 0% 11 2
0.9523809695.2%
 

Contributing tests

This file is covered by 7 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.LinkedHashMap;
23    import java.util.List;
24    import java.util.Map;
25    import java.util.Queue;
26    import java.util.concurrent.ConcurrentLinkedQueue;
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.xwiki.component.annotation.Component;
34    import org.xwiki.component.phase.Initializable;
35    import org.xwiki.component.phase.InitializationException;
36    import org.xwiki.rendering.block.Block;
37    import org.xwiki.rendering.block.LinkBlock;
38    import org.xwiki.rendering.block.MetaDataBlock;
39    import org.xwiki.rendering.block.match.ClassBlockMatcher;
40    import org.xwiki.rendering.block.match.MetadataBlockMatcher;
41    import org.xwiki.rendering.listener.MetaData;
42    import org.xwiki.rendering.listener.reference.ResourceType;
43    import org.xwiki.rendering.transformation.AbstractTransformation;
44    import org.xwiki.rendering.transformation.TransformationContext;
45    import org.xwiki.rendering.transformation.TransformationException;
46    import org.xwiki.rendering.transformation.linkchecker.LinkContextDataProvider;
47   
48    /**
49    * Looks for external URLs in links and verify their status (ok, broken, etc). In order to get good performances this is
50    * done asynchronously in a Thread and same links are checked only every N minutes.
51    *
52    * @version $Id: 92c6ad25b02705d4b7ff4d9877067e3f4ac809c4 $
53    * @since 3.3M1
54    */
55    @Component
56    @Named("linkchecker")
57    @Singleton
 
58    public class LinkCheckerTransformation extends AbstractTransformation implements Initializable
59    {
60    /**
61    * Anti-flood mechanism. We only allow adding links to check in the queue if it currently has less than
62    * MAX_LINKS_IN_QUEUE already. This is a safeguard for the following:
63    * - if the checker thread has some issues and thus the queue isn't getting unpiled then this ensures that it won't
64    * grow more which would slowly use up all the memory...
65    * - we don't swamp the system with links to check (for ex if lots of users go to pages with links the queue can
66    * quickly grow large)
67    * In any case, the links will be checked again later when the pages are rendered again.
68    */
69    static final int MAX_LINKS_IN_QUEUE = 10;
70   
71    @Inject
72    private LinkCheckerThread checkerThread;
73   
74    @Inject
75    private Provider<List<LinkContextDataProvider>> linkContextDataProvidersProvider;
76   
77    /**
78    * The link queue that the checker thread will use to check links. We use a separate checker thread and a queue
79    * in order to have good performance so that this transformation doesn't slow down the rendering of content.
80    */
81    private Queue<LinkQueueItem> linkQueue = new ConcurrentLinkedQueue<>();
82   
83    /**
84    * Start a Thread in charge of reading links to check from the Checking queue and checking them.
85    *
86    * @throws InitializationException not used
87    */
 
88  8 toggle @Override
89    public void initialize() throws InitializationException
90    {
91  8 this.checkerThread.setName("Link Checker Thread");
92  8 this.checkerThread.startProcessing(getLinkQueue());
93    }
94   
 
95  14 toggle @Override
96    public void transform(Block source, TransformationContext context) throws TransformationException
97    {
98    // Note that we don't check for pages to excludes here because this transformation is running in the main
99    // thread and is executed for each page view and thus needs to be as fast as possible. The exclusion handling
100    // is thus done in the Link Checker Thread.
101   
102    // Anti-flood mechanism, only add items in the queue if there are less than MAX_LINKS_IN_QUEUE. This means that
103    // if the queue has MAX_LINKS_IN_QUEUE or more elements already the links from the current page being rendered
104    // will not be verified. They'll get their chance the next time the page is visited again...
105  14 if (getLinkQueue().size() < MAX_LINKS_IN_QUEUE) {
106  13 for (LinkBlock linkBlock : source.<LinkBlock>getBlocks(
107    new ClassBlockMatcher(LinkBlock.class), Block.Axes.DESCENDANT))
108    {
109  24 if (linkBlock.getReference().getType().equals(ResourceType.URL)) {
110    // This is a link pointing to an external URL, add it to the queue for processing (i.e. checking).
111  24 String linkReference = linkBlock.getReference().getReference();
112  24 String contentReference = extractSourceContentReference(linkBlock);
113    // If there's no content reference then use a default name of "default"
114  24 if (contentReference == null) {
115  20 contentReference = "default";
116    }
117    // Add Link Context Data
118  24 Map<String, Object> linkContextData = createLinkContextData(linkReference, contentReference);
119  24 this.linkQueue.add(new LinkQueueItem(linkReference, contentReference, linkContextData));
120    }
121    }
122    }
123    }
124   
125    /**
126    * Stops the checking thread. Should be called when the application is stopped for a clean shutdown.
127    *
128    * @throws InterruptedException if the thread failed to be stopped
129    */
 
130  7 toggle public void stopLinkCheckerThread() throws InterruptedException
131    {
132  7 this.checkerThread.stopProcessing();
133    // Wait till the thread goes away
134  7 this.checkerThread.join();
135    }
136   
137    /**
138    * @return the checker queue containing all pending links to check
139    */
 
140  24 toggle public Queue<LinkQueueItem> getLinkQueue()
141    {
142  24 return this.linkQueue;
143    }
144   
145    /**
146    * @param linkReference the reference to the link to check (usually a URL)
147    * @param contentReference the reference to the content containing the link to check
148    * @return context data to provide more information about the link being checked (for example it could be useful
149    * in some situations to store the HTTP request leading to the link being checked since there could be
150    * HTTP query string parameters useful to see to understand why such a link was generated in the content)
151    */
 
152  24 toggle private Map<String, Object> createLinkContextData(String linkReference, String contentReference)
153    {
154    // For performance reason we don't want to store an empty map in the Link state cache when there are no
155    // context data.
156  24 Map<String, Object> linkContextData = null;
157  24 for (LinkContextDataProvider linkContextDataProvider : this.linkContextDataProvidersProvider.get()) {
158  4 Map<String, Object> contextData =
159    linkContextDataProvider.getContextData(linkReference, contentReference);
160  4 if (linkContextData == null) {
161  4 linkContextData = new LinkedHashMap<>(contextData.size());
162    }
163  4 linkContextData.putAll(contextData);
164    }
165  24 return linkContextData;
166    }
167   
168    /**
169    * @param source the blocks from where to try to extract the source content
170    * @return the source content reference or null if none is found
171    */
 
172  24 toggle private String extractSourceContentReference(Block source)
173    {
174  24 String contentSource = null;
175  24 MetaDataBlock metaDataBlock =
176    source.getFirstBlock(new MetadataBlockMatcher(MetaData.SOURCE), Block.Axes.ANCESTOR);
177  24 if (metaDataBlock != null) {
178  4 contentSource = (String) metaDataBlock.getMetaData().getMetaData(MetaData.SOURCE);
179    }
180  24 return contentSource;
181    }
182    }