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}