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.LinkParsingOperations;
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_tabindex = null;
069    private String m_templatefile = null;
070
071    private boolean m_absolute = false;
072    private boolean m_overrideAbsolute = false;
073
074    private Map<String, String> m_containedParams;
075
076    private BodyContent m_bodyContent;
077
078    public void initTag()
079    {
080        super.initTag();
081        m_version = m_cssClass = m_style = m_title = m_target = m_compareToVersion = m_rel = m_jsp = m_ref = m_accesskey = m_templatefile = null;
082        m_context = WikiContext.VIEW;
083        m_containedParams = new HashMap<String, String>();
084        m_absolute = false;
085    }
086
087    public void setTemplatefile( String key )
088    {
089        m_templatefile = key;
090    }
091
092    public void setAccessKey( String key )
093    {
094        m_accesskey = key;
095    }
096
097    public void setAbsolute( String arg )
098    {
099        m_overrideAbsolute = true;
100        m_absolute = TextUtil.isPositive( arg );
101    }
102
103    public String getVersion()
104    {
105        return m_version;
106    }
107
108    public void setVersion( String arg )
109    {
110        m_version = arg;
111    }
112
113    public void setCssClass( String arg )
114    {
115        m_cssClass = arg;
116    }
117
118    public void setStyle( String style )
119    {
120        m_style = style;
121    }
122
123    public void setTitle( String title )
124    {
125        m_title = title;
126    }
127
128    public void setTarget( String target )
129    {
130        m_target = target;
131    }
132
133    public void setTabindex( String tabindex )
134    {
135        m_tabindex = tabindex;
136    }
137
138    public void setCompareToVersion( String ver )
139    {
140        m_compareToVersion = ver;
141    }
142
143    public void setRel( String rel )
144    {
145        m_rel = rel;
146    }
147
148    public void setRef( String ref )
149    {
150        m_ref = ref;
151    }
152
153    public void setJsp( String jsp )
154    {
155        m_jsp = jsp;
156    }
157
158    public void setContext( String context )
159    {
160        m_context = context;
161    }
162
163    /**
164     * Support for ParamTag supplied parameters in body.
165     */
166    public void setContainedParameter( String name, String value )
167    {
168        if( name != null )
169        {
170            if( m_containedParams == null )
171            {
172                m_containedParams = new HashMap<String, String>();
173            }
174            m_containedParams.put( name, value );
175        }
176    }
177
178
179    /**
180     *  This method figures out what kind of an URL should be output.  It mirrors heavily
181     *  on JSPWikiMarkupParser.handleHyperlinks();
182     *
183     * @return the URL
184     * @throws ProviderException
185     */
186    private String figureOutURL()
187        throws ProviderException
188    {
189        String url = null;
190        WikiEngine engine = m_wikiContext.getEngine();
191
192        if( m_pageName == null )
193        {
194            WikiPage page = m_wikiContext.getPage();
195
196            if( page != null )
197            {
198                m_pageName = page.getName();
199            }
200        }
201
202        if( m_templatefile != null )
203        {
204            String params = addParamsForRecipient( null, m_containedParams );
205            String template = engine.getTemplateDir();
206            url = engine.getURL( WikiContext.NONE, "templates/"+template+"/"+m_templatefile, params, false );
207        }
208        else if( m_jsp != null )
209        {
210            String params = addParamsForRecipient( null, m_containedParams );
211            //url = m_wikiContext.getURL( WikiContext.NONE, m_jsp, params );
212            url = engine.getURL( WikiContext.NONE, m_jsp, params, m_absolute );
213        }
214        else if( m_ref != null )
215        {
216            int interwikipoint;
217
218            if( new LinkParsingOperations( m_wikiContext ).isExternalLink(m_ref) )
219            {
220                url = m_ref;
221            }
222            else if( (interwikipoint = m_ref.indexOf(":")) != -1 )
223            {
224                String extWiki = m_ref.substring( 0, interwikipoint );
225                String wikiPage = m_ref.substring( interwikipoint+1 );
226
227                url = engine.getInterWikiURL( extWiki );
228
229                if( url != null )
230                {
231                    url = TextUtil.replaceString( url, "%s", wikiPage );
232                }
233            }
234            else if( m_ref.startsWith("#") )
235            {
236                // Local link
237            }
238            else if( TextUtil.isNumber(m_ref) )
239            {
240                // Reference
241            }
242            else
243            {
244                int hashMark = -1;
245
246                String parms = (m_version != null) ? "version="+getVersion() : null;
247
248                //
249                //  Internal wiki link, but is it an attachment link?
250                //
251                WikiPage p = engine.getPage( m_pageName );
252
253                if( p instanceof Attachment )
254                {
255                    url = m_wikiContext.getURL( WikiContext.ATTACH, m_pageName );
256                }
257                else if( (hashMark = m_ref.indexOf('#')) != -1 )
258                {
259                    // It's an internal Wiki link, but to a named section
260
261                    String namedSection = m_ref.substring( hashMark+1 );
262                    String reallink     = m_ref.substring( 0, hashMark );
263
264                    reallink = MarkupParser.cleanLink( reallink );
265
266                    String matchedLink;
267                    String sectref = "";
268                    if( (matchedLink = engine.getFinalPageName( reallink )) != null )
269                    {
270                        sectref = "section-"+engine.encodeName(matchedLink)+"-"+namedSection;
271                        sectref = "#"+sectref.replace('%', '_');
272                    }
273                    else
274                    {
275                        matchedLink = reallink;
276                    }
277
278                    url = makeBasicURL( m_context, matchedLink, parms, m_absolute ) + sectref;
279                }
280                else
281                {
282                    String reallink = MarkupParser.cleanLink( m_ref );
283
284                    url = makeBasicURL( m_context, reallink, parms, m_absolute );
285                }
286            }
287        }
288        else if( m_pageName != null && m_pageName.length() > 0 )
289        {
290            WikiPage p = engine.getPage( m_pageName );
291
292            String parms = (m_version != null) ? "version="+getVersion() : null;
293
294            parms = addParamsForRecipient( parms, m_containedParams );
295
296            if( p instanceof Attachment )
297            {
298                String ctx = m_context;
299                // Switch context appropriately when attempting to view an
300                // attachment, but don't override the context setting otherwise
301                if( m_context == null || m_context.equals( WikiContext.VIEW ) )
302                {
303                    ctx = WikiContext.ATTACH;
304                }
305                url = engine.getURL( ctx, m_pageName, parms, m_absolute );
306                //url = m_wikiContext.getURL( ctx, m_pageName, parms );
307            }
308            else
309            {
310                url = makeBasicURL( m_context, m_pageName, parms, m_absolute );
311            }
312        }
313        else
314        {
315            String page = engine.getFrontPage();
316            url = makeBasicURL( m_context, page, null, m_absolute );
317        }
318
319        return url;
320    }
321
322    private String addParamsForRecipient( String addTo, Map< String, String > params )
323    {
324        if( params == null || params.size() == 0 )
325        {
326            return addTo;
327        }
328        StringBuilder buf = new StringBuilder();
329        Iterator< Map.Entry< String, String > > it = params.entrySet().iterator();
330        while( it.hasNext() )
331        {
332            Map.Entry< String, String > e = it.next();
333            String n = e.getKey();
334            String v = e.getValue();
335            buf.append( n );
336            buf.append( "=" );
337            buf.append( v );
338            if( it.hasNext() )
339            {
340                buf.append( "&amp;" );
341            }
342        }
343        if( addTo == null )
344        {
345            return buf.toString();
346        }
347        if( !addTo.endsWith( "&amp;" ) )
348        {
349            return addTo + "&amp;" + buf.toString();
350        }
351        return addTo + buf.toString();
352    }
353
354    private String makeBasicURL( String context, String page, String parms, boolean absolute )
355    {
356        String url;
357        WikiEngine engine = m_wikiContext.getEngine();
358
359        if( context.equals( WikiContext.DIFF ) )
360        {
361            int r1 = 0;
362            int r2 = 0;
363
364            if( DiffLinkTag.VER_LATEST.equals(getVersion()) )
365            {
366                WikiPage latest = engine.getPage( page, WikiProvider.LATEST_VERSION );
367
368                r1 = latest.getVersion();
369            }
370            else if( DiffLinkTag.VER_PREVIOUS.equals(getVersion()) )
371            {
372                r1 = m_wikiContext.getPage().getVersion() - 1;
373                r1 = (r1 < 1 ) ? 1 : r1;
374            }
375            else if( DiffLinkTag.VER_CURRENT.equals(getVersion()) )
376            {
377                r1 = m_wikiContext.getPage().getVersion();
378            }
379            else
380            {
381                r1 = Integer.parseInt( getVersion() );
382            }
383
384            if( DiffLinkTag.VER_LATEST.equals(m_compareToVersion) )
385            {
386                WikiPage latest = engine.getPage( page, WikiProvider.LATEST_VERSION );
387
388                r2 = latest.getVersion();
389            }
390            else if( DiffLinkTag.VER_PREVIOUS.equals(m_compareToVersion) )
391            {
392                r2 = m_wikiContext.getPage().getVersion() - 1;
393                r2 = (r2 < 1 ) ? 1 : r2;
394            }
395            else if( DiffLinkTag.VER_CURRENT.equals(m_compareToVersion) )
396            {
397                r2 = m_wikiContext.getPage().getVersion();
398            }
399            else
400            {
401                r2 = Integer.parseInt( m_compareToVersion );
402            }
403
404            parms = "r1="+r1+"&amp;r2="+r2;
405        }
406
407        //url = m_wikiContext.getURL( m_context, m_pageName, parms );
408        url = engine.getURL( m_context, m_pageName, parms, m_absolute );
409
410        return url;
411    }
412
413    public int doWikiStartTag() throws Exception
414    {
415        return EVAL_BODY_BUFFERED;
416    }
417
418    public int doEndTag()
419    {
420        try
421        {
422            WikiEngine engine = m_wikiContext.getEngine();
423
424            if( !m_overrideAbsolute )
425            {
426                // TODO: see WikiContext.getURL(); this check needs to be specified somewhere.
427                m_absolute = "absolute".equals( engine.getWikiProperties().getProperty( WikiEngine.PROP_REFSTYLE ) );
428            }
429
430            JspWriter out = pageContext.getOut();
431            String url = figureOutURL();
432
433            StringBuilder sb = new StringBuilder( 20 );
434
435            sb.append( (m_cssClass != null)   ? "class=\""+m_cssClass+"\" " : "" );
436            sb.append( (m_style != null)   ? "style=\""+m_style+"\" " : "" );
437            sb.append( (m_target != null ) ? "target=\""+m_target+"\" " : "" );
438            sb.append( (m_title != null )  ? "title=\""+m_title+"\" " : "" );
439            sb.append( (m_rel != null )    ? "rel=\""+m_rel+"\" " : "" );
440            sb.append( (m_accesskey != null) ? "accesskey=\""+m_accesskey+"\" " : "" );
441            sb.append( (m_tabindex != null) ? "tabindex=\""+m_tabindex+"\" " : "" );
442
443            if( engine.getPage( m_pageName ) instanceof Attachment )
444            {
445                sb.append( engine.getAttachmentManager().forceDownload( m_pageName ) ? "download " : "" );
446            }
447
448            switch( m_format )
449            {
450              case URL:
451                out.print( url );
452                break;
453              default:
454              case ANCHOR:
455                out.print("<a "+sb.toString()+" href=\""+url+"\">");
456                break;
457            }
458
459            // Add any explicit body content. This is not the intended use
460            // of LinkTag, but happens to be the way it has worked previously.
461            if( m_bodyContent != null )
462            {
463                String linktext = m_bodyContent.getString().trim();
464                out.write( linktext );
465            }
466
467            //  Finish off by closing opened anchor
468            if( m_format == ANCHOR ) out.print("</a>");
469        }
470        catch( Exception e )
471        {
472            // Yes, we want to catch all exceptions here, including RuntimeExceptions
473            log.error( "Tag failed", e );
474        }
475
476        return EVAL_PAGE;
477    }
478
479    public void setBodyContent( BodyContent bc )
480    {
481        m_bodyContent = bc;
482    }
483
484    public void doInitBody() throws JspException
485    {
486    }
487}