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