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 public void execute(Sink sink, MacroRequest request) throws MacroExecutionException { 075 String id = (String) request.getParameter("id"); 076 077 String urlParam = (String) request.getParameter("url"); 078 079 String fileParam = (String) request.getParameter("file"); 080 081 String debugParam = (String) request.getParameter("debug"); 082 083 if (debugParam != null) { 084 this.debug = Boolean.parseBoolean(debugParam); 085 } 086 087 String ignoreDownloadErrorParam = (String) request.getParameter("ignoreDownloadError"); 088 089 if (ignoreDownloadErrorParam != null) { 090 this.ignoreDownloadError = Boolean.parseBoolean(ignoreDownloadErrorParam); 091 } 092 093 boolean verbatim = true; 094 095 String verbatimParam = (String) request.getParameter("verbatim"); 096 097 if (verbatimParam != null && !"".equals(verbatimParam)) { 098 verbatim = Boolean.valueOf(verbatimParam); 099 } 100 101 boolean source = true; 102 103 String sourceParam = (String) request.getParameter("source"); 104 105 if (sourceParam != null && !"".equals(sourceParam)) { 106 source = Boolean.valueOf(sourceParam); 107 } 108 109 String encoding = (String) request.getParameter("encoding"); 110 111 URL url; 112 113 if (!(urlParam == null || urlParam.isEmpty())) { 114 try { 115 url = new URL(urlParam); 116 } catch (MalformedURLException e) { 117 throw new IllegalArgumentException(urlParam + " is a malformed URL", e); 118 } 119 } else if (!(fileParam == null || fileParam.isEmpty())) { 120 File f = new File(fileParam); 121 122 if (!f.isAbsolute()) { 123 f = new File(request.getBasedir(), fileParam); 124 } 125 126 try { 127 url = f.toURI().toURL(); 128 } catch (MalformedURLException e) { 129 throw new IllegalArgumentException(fileParam + " is a malformed URL", e); 130 } 131 } else { 132 throw new IllegalArgumentException("Either the 'url' or the 'file' param has to be provided"); 133 } 134 135 StringBuffer snippet; 136 137 try { 138 snippet = getSnippet(url, encoding, id); 139 } catch (IOException e) { 140 throw new MacroExecutionException("Error reading snippet", e); 141 } 142 143 if (verbatim) { 144 sink.verbatim(source ? SinkEventAttributeSet.SOURCE : null); 145 146 sink.text(snippet.toString()); 147 148 sink.verbatim_(); 149 } else { 150 sink.rawText(snippet.toString()); 151 } 152 } 153 154 /** 155 * Return a snippet of the given url. 156 * 157 * @param url The URL to parse. 158 * @param encoding The encoding of the URL to parse. 159 * @param id The id of the snippet. 160 * @return The snippet. 161 * @throws IOException if something goes wrong. 162 */ 163 private StringBuffer getSnippet(URL url, String encoding, String id) throws IOException { 164 StringBuffer result; 165 166 String cachedSnippet = getCachedSnippet(url, id); 167 168 if (cachedSnippet != null) { 169 result = new StringBuffer(cachedSnippet); 170 171 if (debug) { 172 result.append("(Served from cache)"); 173 } 174 } else { 175 try { 176 result = new SnippetReader(url, encoding).readSnippet(id); 177 cacheSnippet(url, id, result.toString()); 178 if (debug) { 179 result.append("(Fetched from url, cache content ") 180 .append(cache) 181 .append(")"); 182 } 183 } catch (IOException e) { 184 if (ignoreDownloadError) { 185 LOGGER.debug("Exception while reading '{}'", url, e); 186 result = new StringBuffer("Error during retrieving content skip as ignoreDownloadError activated."); 187 } else { 188 throw e; 189 } 190 } 191 } 192 return result; 193 } 194 195 /** 196 * Return a snippet from the cache. 197 * 198 * @param url The URL to parse. 199 * @param id The id of the snippet. 200 * @return The snippet. 201 */ 202 private String getCachedSnippet(URL url, String id) { 203 if (isCacheTimedout(url, id)) { 204 removeFromCache(url, id); 205 } 206 return cache.get(globalSnippetId(url, id)); 207 } 208 209 /** 210 * Return true if the snippet has been cached longer than 211 * the current timeout. 212 * 213 * @param url The URL to parse. 214 * @param id The id of the snippet. 215 * @return True if timeout exceeded. 216 */ 217 boolean isCacheTimedout(URL url, String id) { 218 return timeInCache(url, id) >= timeout; 219 } 220 221 /** 222 * Return the time the snippet has been cached. 223 * 224 * @param url The URL to parse. 225 * @param id The id of the snippet. 226 * @return The cache time. 227 */ 228 long timeInCache(URL url, String id) { 229 return System.currentTimeMillis() - getTimeCached(url, id); 230 } 231 232 /** 233 * Return the absolute value of when the snippet has been cached. 234 * 235 * @param url The URL to parse. 236 * @param id The id of the snippet. 237 * @return The cache time. 238 */ 239 long getTimeCached(URL url, String id) { 240 String globalId = globalSnippetId(url, id); 241 242 return timeCached.containsKey(globalId) ? timeCached.get(globalId) : 0; 243 } 244 245 /** 246 * Removes the snippet from the cache. 247 * 248 * @param url The URL to parse. 249 * @param id The id of the snippet. 250 */ 251 private void removeFromCache(URL url, String id) { 252 String globalId = globalSnippetId(url, id); 253 254 timeCached.remove(globalId); 255 256 cache.remove(globalId); 257 } 258 259 /** 260 * Return a global identifier for the snippet. 261 * 262 * @param url The URL to parse. 263 * @param id The id of the snippet. 264 * @return An identifier, concatenated url and id, 265 * or just url.toString() if id is empty or null. 266 */ 267 private String globalSnippetId(URL url, String id) { 268 if (id == null || id.isEmpty()) { 269 return url.toString(); 270 } 271 272 return url + " " + id; 273 } 274 275 /** 276 * Puts the given snippet into the cache. 277 * 278 * @param url The URL to parse. 279 * @param id The id of the snippet. 280 * @param content The content of the snippet. 281 */ 282 public void cacheSnippet(URL url, String id, String content) { 283 cache.put(globalSnippetId(url, id), content); 284 285 timeCached.put(globalSnippetId(url, id), System.currentTimeMillis()); 286 } 287 288 /** 289 * Set the cache timeout. 290 * 291 * @param time The timeout to set. 292 */ 293 public void setCacheTimeout(int time) { 294 this.timeout = time; 295 } 296}