001/*
002    Licensed to the Apache Software Foundation (ASF) under one
003    or more contributor license agreements.  See the NOTICE file
004    distributed with this work for additional information
005    regarding copyright ownership.  The ASF licenses this file
006    to you under the Apache License, Version 2.0 (the
007    "License"); you may not use this file except in compliance
008    with the License.  You may obtain a copy of the License at
009
010       http://www.apache.org/licenses/LICENSE-2.0
011
012    Unless required by applicable law or agreed to in writing,
013    software distributed under the License is distributed on an
014    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015    KIND, either express or implied.  See the License for the
016    specific language governing permissions and limitations
017    under the License.
018 */
019package org.apache.wiki.tags;
020
021import java.util.HashMap;
022import java.util.Iterator;
023import java.util.Map;
024
025import javax.servlet.jsp.JspException;
026import javax.servlet.jsp.JspWriter;
027import javax.servlet.jsp.tagext.BodyContent;
028import javax.servlet.jsp.tagext.BodyTag;
029
030import org.apache.log4j.Logger;
031import org.apache.wiki.WikiContext;
032import org.apache.wiki.WikiEngine;
033import org.apache.wiki.WikiPage;
034import org.apache.wiki.WikiProvider;
035import org.apache.wiki.api.exceptions.ProviderException;
036import org.apache.wiki.attachment.Attachment;
037import org.apache.wiki.parser.JSPWikiMarkupParser;
038import org.apache.wiki.parser.MarkupParser;
039import org.apache.wiki.util.TextUtil;
040
041/**
042 *  Provides a generic link tag for all kinds of linking
043 *  purposes.
044 *  <p>
045 *  If parameter <i>jsp</i> is defined, constructs a URL pointing
046 *  to the specified JSP page, under the baseURL known by the WikiEngine.
047 *  Any ParamTag name-value pairs contained in the body are added to this
048 *  URL to provide support for arbitrary JSP calls.
049 *  <p>
050 *  @since 2.3.50
051 */
052public class LinkTag extends WikiLinkTag implements ParamHandler, BodyTag {
053
054    static final long serialVersionUID = 0L;
055    private static final Logger log = Logger.getLogger( LinkTag.class );
056
057    private String m_version = null;
058    private String m_cssClass= null;
059    private String m_style   = null;
060    private String m_title   = null;
061    private String m_target  = null;
062    private String m_compareToVersion = null;
063    private String m_rel       = null;
064    private String m_jsp     = null;
065    private String m_ref     = null;
066    private String m_context = WikiContext.VIEW;
067    private String m_accesskey = null;
068    private String m_templatefile = null;
069
070    private boolean m_absolute = false;
071    private boolean m_overrideAbsolute = false;
072
073    private Map<String, String> m_containedParams;
074
075    private BodyContent m_bodyContent;
076
077    public void initTag()
078    {
079        super.initTag();
080        m_version = m_cssClass = m_style = m_title = m_target = m_compareToVersion = m_rel = m_jsp = m_ref = m_accesskey = m_templatefile = null;
081        m_context = WikiContext.VIEW;
082        m_containedParams = new HashMap<String, String>();
083        m_absolute = false;
084    }
085
086    public void setTemplatefile( String key )
087    {
088        m_templatefile = key;
089    }
090
091    public void setAccessKey( String key )
092    {
093        m_accesskey = key;
094    }
095
096    public void setAbsolute( String arg )
097    {
098        m_overrideAbsolute = true;
099        m_absolute = TextUtil.isPositive( arg );
100    }
101
102    public String getVersion()
103    {
104        return m_version;
105    }
106
107    public void setVersion( String arg )
108    {
109        m_version = arg;
110    }
111
112    public void setCssClass( String arg )
113    {
114        m_cssClass = arg;
115    }
116
117    public void setStyle( String style )
118    {
119        m_style = style;
120    }
121
122    public void setTitle( String title )
123    {
124        m_title = title;
125    }
126
127    public void setTarget( String target )
128    {
129        m_target = target;
130    }
131
132    public void setCompareToVersion( String ver )
133    {
134        m_compareToVersion = ver;
135    }
136
137    public void setRel( String rel )
138    {
139        m_rel = rel;
140    }
141
142    public void setRef( String ref )
143    {
144        m_ref = ref;
145    }
146
147    public void setJsp( String jsp )
148    {
149        m_jsp = jsp;
150    }
151
152    public void setContext( String context )
153    {
154        m_context = context;
155    }
156
157    /**
158     * Support for ParamTag supplied parameters in body.
159     */
160    public void setContainedParameter( String name, String value )
161    {
162        if( name != null )
163        {
164            if( m_containedParams == null )
165            {
166                m_containedParams = new HashMap<String, String>();
167            }
168            m_containedParams.put( name, value );
169        }
170    }
171
172
173    /**
174     *  This method figures out what kind of an URL should be output.  It mirrors heavily
175     *  on JSPWikiMarkupParser.handleHyperlinks();
176     *
177     * @return the URL
178     * @throws ProviderException
179     */
180    private String figureOutURL()
181        throws ProviderException
182    {
183        String url = null;
184        WikiEngine engine = m_wikiContext.getEngine();
185
186        if( m_pageName == null )
187        {
188            WikiPage page = m_wikiContext.getPage();
189
190            if( page != null )
191            {
192                m_pageName = page.getName();
193            }
194        }
195
196        if( m_templatefile != null )
197        {
198            String params = addParamsForRecipient( null, m_containedParams );
199            String template = engine.getTemplateDir();
200            url = engine.getURL( WikiContext.NONE, "templates/"+template+"/"+m_templatefile, params, false );
201        }
202        else if( m_jsp != null )
203        {
204            String params = addParamsForRecipient( null, m_containedParams );
205            //url = m_wikiContext.getURL( WikiContext.NONE, m_jsp, params );
206            url = engine.getURL( WikiContext.NONE, m_jsp, params, m_absolute );
207        }
208        else if( m_ref != null )
209        {
210            int interwikipoint;
211
212            if( JSPWikiMarkupParser.isExternalLink(m_ref) )
213            {
214                url = m_ref;
215            }
216            else if( (interwikipoint = m_ref.indexOf(":")) != -1 )
217            {
218                String extWiki = m_ref.substring( 0, interwikipoint );
219                String wikiPage = m_ref.substring( interwikipoint+1 );
220
221                url = engine.getInterWikiURL( extWiki );
222
223                if( url != null )
224                {
225                    url = TextUtil.replaceString( url, "%s", wikiPage );
226                }
227            }
228            else if( m_ref.startsWith("#") )
229            {
230                // Local link
231            }
232            else if( TextUtil.isNumber(m_ref) )
233            {
234                // Reference
235            }
236            else
237            {
238                int hashMark = -1;
239
240                String parms = (m_version != null) ? "version="+getVersion() : null;
241
242                //
243                //  Internal wiki link, but is it an attachment link?
244                //
245                WikiPage p = engine.getPage( m_pageName );
246
247                if( p instanceof Attachment )
248                {
249                    url = m_wikiContext.getURL( WikiContext.ATTACH, m_pageName );
250                }
251                else if( (hashMark = m_ref.indexOf('#')) != -1 )
252                {
253                    // It's an internal Wiki link, but to a named section
254
255                    String namedSection = m_ref.substring( hashMark+1 );
256                    String reallink     = m_ref.substring( 0, hashMark );
257
258                    reallink = MarkupParser.cleanLink( reallink );
259
260                    String matchedLink;
261                    String sectref = "";
262                    if( (matchedLink = engine.getFinalPageName( reallink )) != null )
263                    {
264                        sectref = "section-"+engine.encodeName(matchedLink)+"-"+namedSection;
265                        sectref = "#"+sectref.replace('%', '_');
266                    }
267                    else
268                    {
269                        matchedLink = reallink;
270                    }
271
272                    url = makeBasicURL( m_context, matchedLink, parms, m_absolute ) + sectref;
273                }
274                else
275                {
276                    String reallink = MarkupParser.cleanLink( m_ref );
277
278                    url = makeBasicURL( m_context, reallink, parms, m_absolute );
279                }
280            }
281        }
282        else if( m_pageName != null && m_pageName.length() > 0 )
283        {
284            WikiPage p = engine.getPage( m_pageName );
285
286            String parms = (m_version != null) ? "version="+getVersion() : null;
287
288            parms = addParamsForRecipient( parms, m_containedParams );
289
290            if( p instanceof Attachment )
291            {
292                String ctx = m_context;
293                // Switch context appropriately when attempting to view an
294                // attachment, but don't override the context setting otherwise
295                if( m_context == null || m_context.equals( WikiContext.VIEW ) )
296                {
297                    ctx = WikiContext.ATTACH;
298                }
299                url = engine.getURL( ctx, m_pageName, parms, m_absolute );
300                //url = m_wikiContext.getURL( ctx, m_pageName, parms );
301            }
302            else
303            {
304                url = makeBasicURL( m_context, m_pageName, parms, m_absolute );
305            }
306        }
307        else
308        {
309            String page = engine.getFrontPage();
310            url = makeBasicURL( m_context, page, null, m_absolute );
311        }
312
313        return url;
314    }
315
316    private String addParamsForRecipient( String addTo, Map< String, String > params )
317    {
318        if( params == null || params.size() == 0 )
319        {
320            return addTo;
321        }
322        StringBuilder buf = new StringBuilder();
323        Iterator< Map.Entry< String, String > > it = params.entrySet().iterator();
324        while( it.hasNext() )
325        {
326            Map.Entry< String, String > e = it.next();
327            String n = e.getKey();
328            String v = e.getValue();
329            buf.append( n );
330            buf.append( "=" );
331            buf.append( v );
332            if( it.hasNext() )
333            {
334                buf.append( "&amp;" );
335            }
336        }
337        if( addTo == null )
338        {
339            return buf.toString();
340        }
341        if( !addTo.endsWith( "&amp;" ) )
342        {
343            return addTo + "&amp;" + buf.toString();
344        }
345        return addTo + buf.toString();
346    }
347
348    private String makeBasicURL( String context, String page, String parms, boolean absolute )
349    {
350        String url;
351        WikiEngine engine = m_wikiContext.getEngine();
352
353        if( context.equals( WikiContext.DIFF ) )
354        {
355            int r1 = 0;
356            int r2 = 0;
357
358            if( DiffLinkTag.VER_LATEST.equals(getVersion()) )
359            {
360                WikiPage latest = engine.getPage( page, WikiProvider.LATEST_VERSION );
361
362                r1 = latest.getVersion();
363            }
364            else if( DiffLinkTag.VER_PREVIOUS.equals(getVersion()) )
365            {
366                r1 = m_wikiContext.getPage().getVersion() - 1;
367                r1 = (r1 < 1 ) ? 1 : r1;
368            }
369            else if( DiffLinkTag.VER_CURRENT.equals(getVersion()) )
370            {
371                r1 = m_wikiContext.getPage().getVersion();
372            }
373            else
374            {
375                r1 = Integer.parseInt( getVersion() );
376            }
377
378            if( DiffLinkTag.VER_LATEST.equals(m_compareToVersion) )
379            {
380                WikiPage latest = engine.getPage( page, WikiProvider.LATEST_VERSION );
381
382                r2 = latest.getVersion();
383            }
384            else if( DiffLinkTag.VER_PREVIOUS.equals(m_compareToVersion) )
385            {
386                r2 = m_wikiContext.getPage().getVersion() - 1;
387                r2 = (r2 < 1 ) ? 1 : r2;
388            }
389            else if( DiffLinkTag.VER_CURRENT.equals(m_compareToVersion) )
390            {
391                r2 = m_wikiContext.getPage().getVersion();
392            }
393            else
394            {
395                r2 = Integer.parseInt( m_compareToVersion );
396            }
397
398            parms = "r1="+r1+"&amp;r2="+r2;
399        }
400
401        //url = m_wikiContext.getURL( m_context, m_pageName, parms );
402        url = engine.getURL( m_context, m_pageName, parms, m_absolute );
403
404        return url;
405    }
406
407    public int doWikiStartTag() throws Exception
408    {
409        return EVAL_BODY_BUFFERED;
410    }
411
412    public int doEndTag()
413    {
414        try
415        {
416            if( !m_overrideAbsolute )
417            {
418                // TODO: see WikiContext.getURL(); this check needs to be specified somewhere.
419                WikiEngine engine = m_wikiContext.getEngine();
420                m_absolute = "absolute".equals( engine.getWikiProperties().getProperty( WikiEngine.PROP_REFSTYLE ) );
421            }
422
423            JspWriter out = pageContext.getOut();
424            String url = figureOutURL();
425
426            StringBuilder sb = new StringBuilder( 20 );
427
428            sb.append( (m_cssClass != null)   ? "class=\""+m_cssClass+"\" " : "" );
429            sb.append( (m_style != null)   ? "style=\""+m_style+"\" " : "" );
430            sb.append( (m_target != null ) ? "target=\""+m_target+"\" " : "" );
431            sb.append( (m_title != null )  ? "title=\""+m_title+"\" " : "" );
432            sb.append( (m_rel != null )    ? "rel=\""+m_rel+"\" " : "" );
433            sb.append( (m_accesskey != null) ? "accesskey=\""+m_accesskey+"\" " : "" );
434
435            switch( m_format )
436            {
437              case URL:
438                out.print( url );
439                break;
440              default:
441              case ANCHOR:
442                out.print("<a "+sb.toString()+" href=\""+url+"\">");
443                break;
444            }
445
446            // Add any explicit body content. This is not the intended use
447            // of LinkTag, but happens to be the way it has worked previously.
448            if( m_bodyContent != null )
449            {
450                String linktext = m_bodyContent.getString().trim();
451                out.write( linktext );
452            }
453
454            //  Finish off by closing opened anchor
455            if( m_format == ANCHOR ) out.print("</a>");
456        }
457        catch( Exception e )
458        {
459            // Yes, we want to catch all exceptions here, including RuntimeExceptions
460            log.error( "Tag failed", e );
461        }
462
463        return EVAL_PAGE;
464    }
465
466    public void setBodyContent( BodyContent bc )
467    {
468        m_bodyContent = bc;
469    }
470
471    public void doInitBody() throws JspException
472    {
473    }
474}