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; 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 getLog().debug( "IOException which reading " + url + ": " + e ); 211 result = new StringBuffer( "Error during retrieving content skip as ignoreDownloadError activated." ); 212 } 213 214 215 } 216 217 return result; 218 } 219 220 /** 221 * Return a snippet from the cache. 222 * 223 * @param url The URL to parse. 224 * @param id The id of the snippet. 225 * @return The snippet. 226 */ 227 private String getCachedSnippet( URL url, String id ) 228 { 229 if ( isCacheTimedout( url, id ) ) 230 { 231 removeFromCache( url, id ); 232 } 233 return cache.get( globalSnippetId( url, id ) ); 234 } 235 236 /** 237 * Return true if the snippet has been cached longer than 238 * the current timeout. 239 * 240 * @param url The URL to parse. 241 * @param id The id of the snippet. 242 * @return True if timeout exceeded. 243 */ 244 boolean isCacheTimedout( URL url, String id ) 245 { 246 return timeInCache( url, id ) >= timeout; 247 } 248 249 /** 250 * Return the time the snippet has been cached. 251 * 252 * @param url The URL to parse. 253 * @param id The id of the snippet. 254 * @return The cache time. 255 */ 256 long timeInCache( URL url, String id ) 257 { 258 return System.currentTimeMillis() - getTimeCached( url, id ); 259 } 260 261 /** 262 * Return the absolute value of when the snippet has been cached. 263 * 264 * @param url The URL to parse. 265 * @param id The id of the snippet. 266 * @return The cache time. 267 */ 268 long getTimeCached( URL url, String id ) 269 { 270 String globalId = globalSnippetId( url, id ); 271 272 return timeCached.containsKey( globalId ) ? timeCached.get( globalId ).longValue() : 0; 273 } 274 275 /** 276 * Removes the snippet from the cache. 277 * 278 * @param url The URL to parse. 279 * @param id The id of the snippet. 280 */ 281 private void removeFromCache( URL url, String id ) 282 { 283 String globalId = globalSnippetId( url, id ); 284 285 timeCached.remove( globalId ); 286 287 cache.remove( globalId ); 288 } 289 290 /** 291 * Return a global identifier for the snippet. 292 * 293 * @param url The URL to parse. 294 * @param id The id of the snippet. 295 * @return An identifier, concatenated url and id, 296 * or just url.toString() if id is empty or null. 297 */ 298 private String globalSnippetId( URL url, String id ) 299 { 300 if ( StringUtils.isEmpty( id ) ) 301 { 302 return url.toString(); 303 } 304 305 return url + " " + id; 306 } 307 308 /** 309 * Puts the given snippet into the cache. 310 * 311 * @param url The URL to parse. 312 * @param id The id of the snippet. 313 * @param content The content of the snippet. 314 */ 315 public void cacheSnippet( URL url, String id, String content ) 316 { 317 cache.put( globalSnippetId( url, id ), content ); 318 319 timeCached.put( globalSnippetId( url, id ), Long.valueOf( System.currentTimeMillis() ) ); 320 } 321 322 /** 323 * Set the cache timeout. 324 * 325 * @param time The timeout to set. 326 */ 327 public void setCacheTimeout( int time ) 328 { 329 this.timeout = time; 330 } 331}