001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.maven.doxia.macro.snippet; 020 021import javax.inject.Named; 022import javax.inject.Singleton; 023 024import java.io.File; 025import java.io.IOException; 026import java.net.MalformedURLException; 027import java.net.URL; 028import java.util.HashMap; 029import java.util.Map; 030 031import org.apache.maven.doxia.macro.AbstractMacro; 032import org.apache.maven.doxia.macro.MacroExecutionException; 033import org.apache.maven.doxia.macro.MacroRequest; 034import org.apache.maven.doxia.sink.Sink; 035import org.apache.maven.doxia.sink.impl.SinkEventAttributeSet; 036import org.slf4j.Logger; 037import org.slf4j.LoggerFactory; 038 039/** 040 * A macro that prints out the (source code) content of a file or a URL. 041 */ 042@Singleton 043@Named("snippet") 044public class SnippetMacro extends AbstractMacro { 045 private static final Logger LOGGER = LoggerFactory.getLogger(SnippetMacro.class); 046 047 /** 048 * Holds the cache. 049 */ 050 private static Map<String, String> cache = new HashMap<>(); 051 052 private static final int HOUR = 60; 053 054 /** 055 * One hour default cache. 056 */ 057 private long timeout = HOUR * HOUR * 1000; 058 059 /** 060 * Holds the time cache. 061 */ 062 private static Map<String, Long> timeCached = new HashMap<>(); 063 064 /** 065 * Debug. 066 */ 067 private boolean debug = false; 068 069 /** 070 * in case of Exception during snippet download error will ignored and empty content returned. 071 */ 072 private boolean ignoreDownloadError = true; 073 074 /** {@inheritDoc} */ 075 public void execute(Sink sink, MacroRequest request) throws MacroExecutionException { 076 String id = (String) request.getParameter("id"); 077 078 String urlParam = (String) request.getParameter("url"); 079 080 String fileParam = (String) request.getParameter("file"); 081 082 String debugParam = (String) request.getParameter("debug"); 083 084 if (debugParam != null) { 085 this.debug = Boolean.parseBoolean(debugParam); 086 } 087 088 String ignoreDownloadErrorParam = (String) request.getParameter("ignoreDownloadError"); 089 090 if (ignoreDownloadErrorParam != null) { 091 this.ignoreDownloadError = Boolean.parseBoolean(ignoreDownloadErrorParam); 092 } 093 094 boolean verbatim = true; 095 096 String verbatimParam = (String) request.getParameter("verbatim"); 097 098 if (verbatimParam != null && !"".equals(verbatimParam)) { 099 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}