001package org.apache.maven.doxia.macro.snippet; 002 003/* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022import org.apache.maven.doxia.macro.AbstractMacro; 023import org.apache.maven.doxia.macro.Macro; 024import org.apache.maven.doxia.macro.MacroExecutionException; 025import org.apache.maven.doxia.macro.MacroRequest; 026import org.apache.maven.doxia.sink.Sink; 027import org.apache.maven.doxia.sink.impl.SinkEventAttributeSet; 028import org.codehaus.plexus.component.annotations.Component; 029import org.codehaus.plexus.util.StringUtils; 030 031import java.io.File; 032import java.io.IOException; 033import java.net.MalformedURLException; 034import java.net.URL; 035import java.util.HashMap; 036import java.util.Map; 037 038/** 039 * A macro that prints out the content of a file or a URL. 040 */ 041@Component( role = Macro.class, hint = "snippet" ) 042public class SnippetMacro 043 extends AbstractMacro 044{ 045 /** 046 * Holds the cache. 047 */ 048 private static Map<String, String> cache = new HashMap<>(); 049 050 private static final int HOUR = 60; 051 052 /** 053 * One hour default cache. 054 */ 055 private long timeout = HOUR * HOUR * 1000; 056 057 /** 058 * Holds the time cache. 059 */ 060 private static Map<String, Long> timeCached = new HashMap<>(); 061 062 /** 063 * Debug. 064 */ 065 private boolean debug = false; 066 067 /** 068 * in case of Exception during snippet download error will ignored and empty content returned. 069 */ 070 private boolean ignoreDownloadError = true; 071 072 /** {@inheritDoc} */ 073 public void execute( Sink sink, MacroRequest request ) 074 throws MacroExecutionException 075 { 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 { 086 this.debug = Boolean.parseBoolean( debugParam ); 087 } 088 089 String ignoreDownloadErrorParam = (String) request.getParameter( "ignoreDownloadError" ); 090 091 if ( ignoreDownloadErrorParam != null ) 092 { 093 this.ignoreDownloadError = Boolean.parseBoolean( ignoreDownloadErrorParam ); 094 } 095 096 boolean verbatim = true; 097 098 String verbatimParam = (String) request.getParameter( "verbatim" ); 099 100 if ( verbatimParam != null && !"".equals( verbatimParam ) ) 101 { 102 verbatim = Boolean.valueOf( verbatimParam ); 103 } 104 105 String encoding = (String) request.getParameter( "encoding" ); 106 107 URL url; 108 109 if ( !StringUtils.isEmpty( urlParam ) ) 110 { 111 try 112 { 113 url = new URL( urlParam ); 114 } 115 catch ( MalformedURLException e ) 116 { 117 throw new IllegalArgumentException( urlParam + " is a malformed URL" ); 118 } 119 } 120 else if ( !StringUtils.isEmpty( fileParam ) ) 121 { 122 File f = new File( fileParam ); 123 124 if ( !f.isAbsolute() ) 125 { 126 f = new File( request.getBasedir(), fileParam ); 127 } 128 129 try 130 { 131 url = f.toURI().toURL(); 132 } 133 catch ( MalformedURLException e ) 134 { 135 throw new IllegalArgumentException( fileParam + " is a malformed URL" ); 136 } 137 } 138 else 139 { 140 throw new IllegalArgumentException( "Either the 'url' or the 'file' param has to be given." ); 141 } 142 143 StringBuffer snippet; 144 145 try 146 { 147 snippet = getSnippet( url, encoding, id ); 148 } 149 catch ( IOException e ) 150 { 151 throw new MacroExecutionException( "Error reading snippet", e ); 152 } 153 154 if ( verbatim ) 155 { 156 sink.verbatim( SinkEventAttributeSet.BOXED ); 157 158 sink.text( snippet.toString() ); 159 160 sink.verbatim_(); 161 } 162 else 163 { 164 sink.rawText( snippet.toString() ); 165 } 166 } 167 168 /** 169 * Return a snippet of the given url. 170 * 171 * @param url The URL to parse. 172 * @param encoding The encoding of the URL to parse. 173 * @param id The id of the snippet. 174 * @return The snippet. 175 * @throws IOException if something goes wrong. 176 */ 177 private StringBuffer getSnippet( URL url, String encoding, String id ) 178 throws IOException 179 { 180 StringBuffer result; 181 182 String cachedSnippet = getCachedSnippet( url, id ); 183 184 if ( cachedSnippet != null ) 185 { 186 result = new StringBuffer( cachedSnippet ); 187 188 if ( debug ) 189 { 190 result.append( "(Served from cache)" ); 191 } 192 } 193 else 194 { 195 try 196 { 197 result = new SnippetReader( url, encoding ).readSnippet( id ); 198 cacheSnippet( url, id, result.toString() ); 199 if ( debug ) 200 { 201 result.append( "(Fetched from url, cache content " ).append( cache ).append( ")" ); 202 } 203 } 204 catch ( IOException e ) 205 { 206 if ( ignoreDownloadError ) 207 { 208 getLog().debug( "IOException which reading " + url + ": " + e ); 209 result = 210 new StringBuffer( "Error during retrieving content skip as ignoreDownloadError activated." ); 211 } 212 else 213 { 214 throw e; 215 } 216 } 217 } 218 return result; 219 } 220 221 /** 222 * Return a snippet from the cache. 223 * 224 * @param url The URL to parse. 225 * @param id The id of the snippet. 226 * @return The snippet. 227 */ 228 private String getCachedSnippet( URL url, String id ) 229 { 230 if ( isCacheTimedout( url, id ) ) 231 { 232 removeFromCache( url, id ); 233 } 234 return cache.get( globalSnippetId( url, id ) ); 235 } 236 237 /** 238 * Return true if the snippet has been cached longer than 239 * the current timeout. 240 * 241 * @param url The URL to parse. 242 * @param id The id of the snippet. 243 * @return True if timeout exceeded. 244 */ 245 boolean isCacheTimedout( URL url, String id ) 246 { 247 return timeInCache( url, id ) >= timeout; 248 } 249 250 /** 251 * Return the time the snippet has been cached. 252 * 253 * @param url The URL to parse. 254 * @param id The id of the snippet. 255 * @return The cache time. 256 */ 257 long timeInCache( URL url, String id ) 258 { 259 return System.currentTimeMillis() - getTimeCached( url, id ); 260 } 261 262 /** 263 * Return the absolute value of when the snippet has been cached. 264 * 265 * @param url The URL to parse. 266 * @param id The id of the snippet. 267 * @return The cache time. 268 */ 269 long getTimeCached( URL url, String id ) 270 { 271 String globalId = globalSnippetId( url, id ); 272 273 return timeCached.containsKey( globalId ) ? timeCached.get( globalId ) : 0; 274 } 275 276 /** 277 * Removes the snippet from the cache. 278 * 279 * @param url The URL to parse. 280 * @param id The id of the snippet. 281 */ 282 private void removeFromCache( URL url, String id ) 283 { 284 String globalId = globalSnippetId( url, id ); 285 286 timeCached.remove( globalId ); 287 288 cache.remove( globalId ); 289 } 290 291 /** 292 * Return a global identifier for the snippet. 293 * 294 * @param url The URL to parse. 295 * @param id The id of the snippet. 296 * @return An identifier, concatenated url and id, 297 * or just url.toString() if id is empty or null. 298 */ 299 private String globalSnippetId( URL url, String id ) 300 { 301 if ( StringUtils.isEmpty( id ) ) 302 { 303 return url.toString(); 304 } 305 306 return url + " " + id; 307 } 308 309 /** 310 * Puts the given snippet into the cache. 311 * 312 * @param url The URL to parse. 313 * @param id The id of the snippet. 314 * @param content The content of the snippet. 315 */ 316 public void cacheSnippet( URL url, String id, String content ) 317 { 318 cache.put( globalSnippetId( url, id ), content ); 319 320 timeCached.put( globalSnippetId( url, id ), System.currentTimeMillis() ); 321 } 322 323 /** 324 * Set the cache timeout. 325 * 326 * @param time The timeout to set. 327 */ 328 public void setCacheTimeout( int time ) 329 { 330 this.timeout = time; 331 } 332}