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}