View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.doxia.macro.snippet;
20  
21  import javax.inject.Named;
22  import javax.inject.Singleton;
23  
24  import java.io.File;
25  import java.io.IOException;
26  import java.net.MalformedURLException;
27  import java.net.URL;
28  import java.util.HashMap;
29  import java.util.Map;
30  
31  import org.apache.maven.doxia.macro.AbstractMacro;
32  import org.apache.maven.doxia.macro.MacroExecutionException;
33  import org.apache.maven.doxia.macro.MacroRequest;
34  import org.apache.maven.doxia.sink.Sink;
35  import org.apache.maven.doxia.sink.impl.SinkEventAttributeSet;
36  import org.slf4j.Logger;
37  import org.slf4j.LoggerFactory;
38  
39  /**
40   * A macro that prints out the (source code) content of a file or a URL.
41   */
42  @Singleton
43  @Named("snippet")
44  public class SnippetMacro extends AbstractMacro {
45      private static final Logger LOGGER = LoggerFactory.getLogger(SnippetMacro.class);
46  
47      /**
48       * Holds the cache.
49       */
50      private static Map<String, String> cache = new HashMap<>();
51  
52      private static final int HOUR = 60;
53  
54      /**
55       * One hour default cache.
56       */
57      private long timeout = HOUR * HOUR * 1000;
58  
59      /**
60       * Holds the time cache.
61       */
62      private static Map<String, Long> timeCached = new HashMap<>();
63  
64      /**
65       * Debug.
66       */
67      private boolean debug = false;
68  
69      /**
70       * in case of Exception during snippet download error will ignored and empty content returned.
71       */
72      private boolean ignoreDownloadError = true;
73  
74      /** {@inheritDoc} */
75      public void execute(Sink sink, MacroRequest request) throws MacroExecutionException {
76          String id = (String) request.getParameter("id");
77  
78          String urlParam = (String) request.getParameter("url");
79  
80          String fileParam = (String) request.getParameter("file");
81  
82          String debugParam = (String) request.getParameter("debug");
83  
84          if (debugParam != null) {
85              this.debug = Boolean.parseBoolean(debugParam);
86          }
87  
88          String ignoreDownloadErrorParam = (String) request.getParameter("ignoreDownloadError");
89  
90          if (ignoreDownloadErrorParam != null) {
91              this.ignoreDownloadError = Boolean.parseBoolean(ignoreDownloadErrorParam);
92          }
93  
94          boolean verbatim = true;
95  
96          String verbatimParam = (String) request.getParameter("verbatim");
97  
98          if (verbatimParam != null && !"".equals(verbatimParam)) {
99              verbatim = Boolean.valueOf(verbatimParam);
100         }
101 
102         boolean source = true;
103 
104         String sourceParam = (String) request.getParameter("source");
105 
106         if (sourceParam != null && !"".equals(sourceParam)) {
107             source = Boolean.valueOf(sourceParam);
108         }
109 
110         String encoding = (String) request.getParameter("encoding");
111 
112         URL url;
113 
114         if (!(urlParam == null || urlParam.isEmpty())) {
115             try {
116                 url = new URL(urlParam);
117             } catch (MalformedURLException e) {
118                 throw new IllegalArgumentException(urlParam + " is a malformed URL", e);
119             }
120         } else if (!(fileParam == null || fileParam.isEmpty())) {
121             File f = new File(fileParam);
122 
123             if (!f.isAbsolute()) {
124                 f = new File(request.getBasedir(), fileParam);
125             }
126 
127             try {
128                 url = f.toURI().toURL();
129             } catch (MalformedURLException e) {
130                 throw new IllegalArgumentException(fileParam + " is a malformed URL", e);
131             }
132         } else {
133             throw new IllegalArgumentException("Either the 'url' or the 'file' param has to be provided");
134         }
135 
136         StringBuffer snippet;
137 
138         try {
139             snippet = getSnippet(url, encoding, id);
140         } catch (IOException e) {
141             throw new MacroExecutionException("Error reading snippet", e);
142         }
143 
144         if (verbatim) {
145             sink.verbatim(source ? SinkEventAttributeSet.SOURCE : null);
146 
147             sink.text(snippet.toString());
148 
149             sink.verbatim_();
150         } else {
151             sink.rawText(snippet.toString());
152         }
153     }
154 
155     /**
156      * Return a snippet of the given url.
157      *
158      * @param url The URL to parse.
159      * @param encoding The encoding of the URL to parse.
160      * @param id  The id of the snippet.
161      * @return The snippet.
162      * @throws IOException if something goes wrong.
163      */
164     private StringBuffer getSnippet(URL url, String encoding, String id) throws IOException {
165         StringBuffer result;
166 
167         String cachedSnippet = getCachedSnippet(url, id);
168 
169         if (cachedSnippet != null) {
170             result = new StringBuffer(cachedSnippet);
171 
172             if (debug) {
173                 result.append("(Served from cache)");
174             }
175         } else {
176             try {
177                 result = new SnippetReader(url, encoding).readSnippet(id);
178                 cacheSnippet(url, id, result.toString());
179                 if (debug) {
180                     result.append("(Fetched from url, cache content ")
181                             .append(cache)
182                             .append(")");
183                 }
184             } catch (IOException e) {
185                 if (ignoreDownloadError) {
186                     LOGGER.debug("Exception while reading '{}'", url, e);
187                     result = new StringBuffer("Error during retrieving content skip as ignoreDownloadError activated.");
188                 } else {
189                     throw e;
190                 }
191             }
192         }
193         return result;
194     }
195 
196     /**
197      * Return a snippet from the cache.
198      *
199      * @param url The URL to parse.
200      * @param id  The id of the snippet.
201      * @return The snippet.
202      */
203     private String getCachedSnippet(URL url, String id) {
204         if (isCacheTimedout(url, id)) {
205             removeFromCache(url, id);
206         }
207         return cache.get(globalSnippetId(url, id));
208     }
209 
210     /**
211      * Return true if the snippet has been cached longer than
212      * the current timeout.
213      *
214      * @param url The URL to parse.
215      * @param id  The id of the snippet.
216      * @return True if timeout exceeded.
217      */
218     boolean isCacheTimedout(URL url, String id) {
219         return timeInCache(url, id) >= timeout;
220     }
221 
222     /**
223      * Return the time the snippet has been cached.
224      *
225      * @param url The URL to parse.
226      * @param id  The id of the snippet.
227      * @return The cache time.
228      */
229     long timeInCache(URL url, String id) {
230         return System.currentTimeMillis() - getTimeCached(url, id);
231     }
232 
233     /**
234      * Return the absolute value of when the snippet has been cached.
235      *
236      * @param url The URL to parse.
237      * @param id  The id of the snippet.
238      * @return The cache time.
239      */
240     long getTimeCached(URL url, String id) {
241         String globalId = globalSnippetId(url, id);
242 
243         return timeCached.containsKey(globalId) ? timeCached.get(globalId) : 0;
244     }
245 
246     /**
247      * Removes the snippet from the cache.
248      *
249      * @param url The URL to parse.
250      * @param id  The id of the snippet.
251      */
252     private void removeFromCache(URL url, String id) {
253         String globalId = globalSnippetId(url, id);
254 
255         timeCached.remove(globalId);
256 
257         cache.remove(globalId);
258     }
259 
260     /**
261      * Return a global identifier for the snippet.
262      *
263      * @param url The URL to parse.
264      * @param id  The id of the snippet.
265      * @return An identifier, concatenated url and id,
266      *         or just url.toString() if id is empty or null.
267      */
268     private String globalSnippetId(URL url, String id) {
269         if (id == null || id.isEmpty()) {
270             return url.toString();
271         }
272 
273         return url + " " + id;
274     }
275 
276     /**
277      * Puts the given snippet into the cache.
278      *
279      * @param url     The URL to parse.
280      * @param id      The id of the snippet.
281      * @param content The content of the snippet.
282      */
283     public void cacheSnippet(URL url, String id, String content) {
284         cache.put(globalSnippetId(url, id), content);
285 
286         timeCached.put(globalSnippetId(url, id), System.currentTimeMillis());
287     }
288 
289     /**
290      * Set the cache timeout.
291      *
292      * @param time The timeout to set.
293      */
294     public void setCacheTimeout(int time) {
295         this.timeout = time;
296     }
297 }