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