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 org.apache.logging.log4j.LogManager;
022import org.apache.logging.log4j.Logger;
023import org.apache.wiki.api.core.Attachment;
024import org.apache.wiki.api.core.ContextEnum;
025import org.apache.wiki.api.core.Engine;
026import org.apache.wiki.api.core.Page;
027import org.apache.wiki.api.exceptions.ProviderException;
028import org.apache.wiki.api.providers.WikiProvider;
029import org.apache.wiki.attachment.AttachmentManager;
030import org.apache.wiki.pages.PageManager;
031import org.apache.wiki.parser.LinkParsingOperations;
032import org.apache.wiki.parser.MarkupParser;
033import org.apache.wiki.util.TextUtil;
034
035import javax.servlet.jsp.JspWriter;
036import javax.servlet.jsp.tagext.BodyContent;
037import javax.servlet.jsp.tagext.BodyTag;
038import java.util.HashMap;
039import java.util.Iterator;
040import java.util.Map;
041
042/**
043 *  Provides a generic link tag for all kinds of linking purposes.
044 *  <p>
045 *  If parameter <i>jsp</i> is defined, constructs a URL pointing to the specified JSP page, under the baseURL known by the Engine.
046 *  Any ParamTag name-value pairs contained in the body are added to this URL to provide support for arbitrary JSP calls.
047 *  <p>
048 *  @since 2.3.50
049 */
050public class LinkTag extends WikiLinkTag implements ParamHandler, BodyTag {
051
052    static final long serialVersionUID = 0L;
053    private static final Logger log = LogManager.getLogger( LinkTag.class );
054
055    private String m_version;
056    private String m_cssClass;
057    private String m_style;
058    private String m_title;
059    private String m_target;
060    private String m_compareToVersion;
061    private String m_rel;
062    private String m_jsp;
063    private String m_ref;
064    private String m_context = ContextEnum.PAGE_VIEW.getRequestContext();
065    private String m_accesskey;
066    private String m_tabindex;
067    private String m_templatefile;
068
069    private Map<String, String> m_containedParams;
070
071    private BodyContent m_bodyContent;
072
073    @Override
074    public void initTag() {
075        super.initTag();
076        m_version = m_cssClass = m_style = m_title = m_target = m_compareToVersion = m_rel = m_jsp = m_ref = m_accesskey = m_templatefile = null;
077        m_context = ContextEnum.PAGE_VIEW.getRequestContext();
078        m_containedParams = new HashMap<>();
079    }
080
081    public void setTemplatefile( final String key )
082    {
083        m_templatefile = key;
084    }
085
086    public void setAccessKey( final String key )
087    {
088        m_accesskey = key;
089    }
090
091    public String getVersion()
092    {
093        return m_version;
094    }
095
096    public void setVersion( final String arg )
097    {
098        m_version = arg;
099    }
100
101    public void setCssClass( final String arg )
102    {
103        m_cssClass = arg;
104    }
105
106    public void setStyle( final String style )
107    {
108        m_style = style;
109    }
110
111    public void setTitle( final String title )
112    {
113        m_title = title;
114    }
115
116    public void setTarget( final String target )
117    {
118        m_target = target;
119    }
120
121    public void setTabindex( final String tabindex )
122    {
123        m_tabindex = tabindex;
124    }
125
126    public void setCompareToVersion( final String ver )
127    {
128        m_compareToVersion = ver;
129    }
130
131    public void setRel( final String rel )
132    {
133        m_rel = rel;
134    }
135
136    public void setRef( final String ref )
137    {
138        m_ref = ref;
139    }
140
141    public void setJsp( final String jsp )
142    {
143        m_jsp = jsp;
144    }
145
146    public void setContext( final String context )
147    {
148        m_context = context;
149    }
150
151    /**
152     * Support for ParamTag supplied parameters in body.
153     */
154    @Override
155    public void setContainedParameter( final String name, final String value ) {
156        if( name != null ) {
157            if( m_containedParams == null ) {
158                m_containedParams = new HashMap<>();
159            }
160            m_containedParams.put( name, value );
161        }
162    }
163
164
165    /**
166     *  This method figures out what kind of an URL should be output.  It mirrors heavily on JSPWikiMarkupParser.handleHyperlinks();
167     *
168     * @return the URL
169     * @throws ProviderException
170     */
171    private String figureOutURL() throws ProviderException {
172        String url = null;
173        final Engine engine = m_wikiContext.getEngine();
174
175        if( m_pageName == null ) {
176            final Page page = m_wikiContext.getPage();
177            if( page != null ) {
178                m_pageName = page.getName();
179            }
180        }
181
182        if( m_templatefile != null ) {
183            final String params = addParamsForRecipient( null, m_containedParams );
184            final String template = engine.getTemplateDir();
185            url = engine.getURL( ContextEnum.PAGE_NONE.getRequestContext(), "templates/"+template+"/"+m_templatefile, params );
186        } else if( m_jsp != null ) {
187            final String params = addParamsForRecipient( null, m_containedParams );
188            //url = m_wikiContext.getURL( ContextEnum.PAGE_NONE.getRequestContext(), m_jsp, params );
189            url = engine.getURL( ContextEnum.PAGE_NONE.getRequestContext(), m_jsp, params );
190        } else if( m_ref != null ) {
191            final int interwikipoint;
192            if( new LinkParsingOperations( m_wikiContext ).isExternalLink(m_ref) ) {
193                url = m_ref;
194            } else if( ( interwikipoint = m_ref.indexOf( ":" ) ) != -1 ) {
195                final String extWiki = m_ref.substring( 0, interwikipoint );
196                final String wikiPage = m_ref.substring( interwikipoint+1 );
197
198                url = engine.getInterWikiURL( extWiki );
199                if( url != null ) {
200                    url = TextUtil.replaceString( url, "%s", wikiPage );
201                }
202            } else if( m_ref.startsWith("#") ) {
203                // Local link
204            } else if( TextUtil.isNumber(m_ref) ) {
205                // Reference
206            } else {
207                final int hashMark;
208
209                final String parms = (m_version != null) ? "version="+getVersion() : null;
210
211                //  Internal wiki link, but is it an attachment link?
212                final Page p = engine.getManager( PageManager.class ).getPage( m_pageName );
213                if( p instanceof Attachment ) {
214                    url = m_wikiContext.getURL( ContextEnum.PAGE_ATTACH.getRequestContext(), m_pageName );
215                } else if( (hashMark = m_ref.indexOf('#')) != -1 ) {
216                    // It's an internal Wiki link, but to a named section
217
218                    final String namedSection = m_ref.substring( hashMark+1 );
219                    String reallink     = m_ref.substring( 0, hashMark );
220                    reallink = MarkupParser.cleanLink( reallink );
221
222                    String matchedLink;
223                    String sectref = "";
224                    if( ( matchedLink = engine.getFinalPageName( reallink ) ) != null ) {
225                        sectref = "section-" + engine.encodeName( matchedLink ) + "-" + namedSection;
226                        sectref = "#" + sectref.replace( '%', '_' );
227                    } else {
228                        matchedLink = reallink;
229                    }
230
231                    url = makeBasicURL( m_context, matchedLink, parms ) + sectref;
232                } else {
233                    final String reallink = MarkupParser.cleanLink( m_ref );
234                    url = makeBasicURL( m_context, reallink, parms );
235                }
236            }
237        } else if( m_pageName != null && !m_pageName.isEmpty() ) {
238            final Page p = engine.getManager( PageManager.class ).getPage( m_pageName );
239
240            String parms = (m_version != null) ? "version="+getVersion() : null;
241
242            parms = addParamsForRecipient( parms, m_containedParams );
243
244            if( p instanceof Attachment ) {
245                String ctx = m_context;
246                // Switch context appropriately when attempting to view an
247                // attachment, but don't override the context setting otherwise
248                if( m_context == null || m_context.equals( ContextEnum.PAGE_VIEW.getRequestContext() ) ) {
249                    ctx = ContextEnum.PAGE_ATTACH.getRequestContext();
250                }
251                url = engine.getURL( ctx, m_pageName, parms );
252                //url = m_wikiContext.getURL( ctx, m_pageName, parms );
253            } else {
254                url = makeBasicURL( m_context, m_pageName, parms );
255            }
256        } else {
257            final String page = engine.getFrontPage();
258            url = makeBasicURL( m_context, page, null );
259        }
260
261        return url;
262    }
263
264    private String addParamsForRecipient( final String addTo, final Map< String, String > params ) {
265        if( params == null || params.size() == 0 ) {
266            return addTo;
267        }
268        final StringBuilder buf = new StringBuilder();
269        final Iterator< Map.Entry< String, String > > it = params.entrySet().iterator();
270        while( it.hasNext() ) {
271            final Map.Entry< String, String > e = it.next();
272            final String n = e.getKey();
273            final String v = e.getValue();
274            buf.append( n );
275            buf.append( "=" );
276            buf.append( v );
277            if( it.hasNext() ) {
278                buf.append( "&amp;" );
279            }
280        }
281        if( addTo == null ) {
282            return buf.toString();
283        }
284        if( !addTo.endsWith( "&amp;" ) ) {
285            return addTo + "&amp;" + buf;
286        }
287        return addTo + buf;
288    }
289
290    private String makeBasicURL( final String context, final String page, String parms ) {
291        final Engine engine = m_wikiContext.getEngine();
292
293        if( context.equals( ContextEnum.PAGE_DIFF.getRequestContext() ) ) {
294            int r1;
295            int r2;
296
297            if( DiffLinkTag.VER_LATEST.equals( getVersion() ) ) {
298                final Page latest = engine.getManager( PageManager.class ).getPage( page, WikiProvider.LATEST_VERSION );
299
300                r1 = latest.getVersion();
301            } else if( DiffLinkTag.VER_PREVIOUS.equals(getVersion()) ) {
302                r1 = m_wikiContext.getPage().getVersion() - 1;
303                r1 = Math.max( r1, 1 );
304            } else if( DiffLinkTag.VER_CURRENT.equals(getVersion()) ) {
305                r1 = m_wikiContext.getPage().getVersion();
306            } else {
307                r1 = Integer.parseInt( getVersion() );
308            }
309
310            if( DiffLinkTag.VER_LATEST.equals(m_compareToVersion) ) {
311                final Page latest = engine.getManager( PageManager.class ).getPage( page, WikiProvider.LATEST_VERSION );
312
313                r2 = latest.getVersion();
314            } else if( DiffLinkTag.VER_PREVIOUS.equals(m_compareToVersion) ) {
315                r2 = m_wikiContext.getPage().getVersion() - 1;
316                r2 = Math.max( r2, 1 );
317            } else if( DiffLinkTag.VER_CURRENT.equals(m_compareToVersion) ) {
318                r2 = m_wikiContext.getPage().getVersion();
319            } else {
320                r2 = Integer.parseInt( m_compareToVersion );
321            }
322
323            parms = "r1="+r1+"&amp;r2="+r2;
324        }
325
326        return engine.getURL( m_context, m_pageName, parms );
327    }
328
329    @Override
330    public int doWikiStartTag() throws Exception {
331        return EVAL_BODY_BUFFERED;
332    }
333
334    @Override
335    public int doEndTag() {
336        try {
337            final Engine engine = m_wikiContext.getEngine();
338            final JspWriter out = pageContext.getOut();
339            final String url = figureOutURL();
340
341            final StringBuilder sb = new StringBuilder( 20 );
342
343            sb.append( (m_cssClass != null)   ? "class=\""+m_cssClass+"\" " : "" );
344            sb.append( (m_style != null)   ? "style=\""+m_style+"\" " : "" );
345            sb.append( (m_target != null ) ? "target=\""+m_target+"\" " : "" );
346            sb.append( (m_title != null )  ? "title=\""+m_title+"\" " : "" );
347            sb.append( (m_rel != null )    ? "rel=\""+m_rel+"\" " : "" );
348            sb.append( (m_accesskey != null) ? "accesskey=\""+m_accesskey+"\" " : "" );
349            sb.append( (m_tabindex != null) ? "tabindex=\""+m_tabindex+"\" " : "" );
350
351            if( engine.getManager( PageManager.class ).getPage( m_pageName ) instanceof Attachment ) {
352                sb.append( engine.getManager( AttachmentManager.class ).forceDownload( m_pageName ) ? "download " : "" );
353            }
354
355            switch( m_format ) {
356              case URL:
357                out.print( url );
358                break;
359              default:
360              case ANCHOR:
361                out.print("<a "+ sb +" href=\""+url+"\">");
362                break;
363            }
364
365            // Add any explicit body content. This is not the intended use of LinkTag, but happens to be the way it has worked previously.
366            if( m_bodyContent != null ) {
367                final String linktext = m_bodyContent.getString().trim();
368                out.write( linktext );
369            }
370
371            //  Finish off by closing opened anchor
372            if( m_format == ANCHOR ) out.print("</a>");
373        } catch( final Exception e ) {
374            // Yes, we want to catch all exceptions here, including RuntimeExceptions
375            log.error( "Tag failed", e );
376        }
377
378        return EVAL_PAGE;
379    }
380
381    @Override
382    public void setBodyContent( final BodyContent bc )
383    {
384        m_bodyContent = bc;
385    }
386
387    @Override
388    public void doInitBody() {
389    }
390
391}