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.ContextEnum;
023import org.apache.wiki.pages.PageManager;
024import org.apache.wiki.util.TextUtil;
025
026import javax.servlet.http.HttpSession;
027import javax.servlet.jsp.JspWriter;
028import java.io.IOException;
029import java.io.Serializable;
030import java.util.LinkedList;
031
032/**
033 * Implement a "breadcrumb" (most recently visited) trail.  This tag can be added to any view jsp page.
034 * Separate breadcrumb trails are not tracked across multiple browser windows.<br>
035 * The optional attributes are:
036 * <p>
037 * <b>maxpages</b>, the number of pages to store, 10 by default<br>
038 * <b>separator</b>, the separator string to use between pages, " | " by default<br>
039 * </p>
040 *
041 * <p>
042 * This class is implemented by storing a breadcrumb trail, which is a
043 * fixed size queue, into a session variable "breadCrumbTrail".
044 * This queue is displayed as a series of links separated by a separator
045 * character.
046 * </p>
047 */
048public class BreadcrumbsTag extends WikiTagBase
049{
050    private static final long serialVersionUID = 0L;
051
052    private static final Logger log = Logger.getLogger(BreadcrumbsTag.class);
053    /** The name of the session attribute representing the breadcrumbtrail */
054    public static final String BREADCRUMBTRAIL_KEY = "breadCrumbTrail";
055    private int m_maxQueueSize = 11;
056    private String m_separator = ", ";
057
058    /**
059     *  {@inheritDoc}
060     */
061    @Override
062    public void initTag()
063    {
064        super.initTag();
065        m_maxQueueSize = 11;
066        m_separator = ", ";
067    }
068
069    /**
070     *  Returns the maxpages.  This may differ from what was set by setMaxpages().
071     *
072     *  @return The current size of the pages.
073     */
074    public int getMaxpages()
075    {
076        return m_maxQueueSize;
077    }
078
079    /**
080     *  Sets how many pages to show.
081     *
082     *  @param maxpages The amount.
083     */
084    public void setMaxpages( final int maxpages)
085    {
086        m_maxQueueSize = maxpages + 1;
087    }
088
089    /**
090     *  Get the separator string.
091     *
092     *  @return The string set in setSeparator()
093     */
094    public String getSeparator()
095    {
096        return m_separator;
097    }
098
099    /**
100     *  Set the separator string.
101     *
102     *  @param separator A string which separates the page names.
103     */
104    public void setSeparator( final String separator)
105    {
106        m_separator = TextUtil.replaceEntities( separator );
107    }
108
109    /**
110     *  {@inheritDoc}
111     */
112    @Override
113    public int doWikiStartTag() throws IOException {
114        final HttpSession session = pageContext.getSession();
115        FixedQueue trail = (FixedQueue) session.getAttribute(BREADCRUMBTRAIL_KEY);
116        final String page = m_wikiContext.getPage().getName();
117
118        if( trail == null ) {
119            trail = new FixedQueue(m_maxQueueSize);
120        } else {
121            //  check if page still exists (could be deleted/renamed by another user)
122            for (int i = 0;i<trail.size();i++) {
123                if( !m_wikiContext.getEngine().getManager( PageManager.class ).wikiPageExists( trail.get( i ) ) ) {
124                    trail.remove(i);
125                }
126            }
127        }
128
129        if( m_wikiContext.getRequestContext().equals( ContextEnum.PAGE_VIEW.getRequestContext() ) ) {
130            if( m_wikiContext.getEngine().getManager( PageManager.class ).wikiPageExists( page ) ) {
131                if( trail.isEmpty() ) {
132                    trail.pushItem( page );
133                } else {
134                    // Don't add the page to the queue if the page was just refreshed
135                    if( !trail.getLast().equals( page ) ) {
136                        trail.pushItem( page );
137                    }
138                }
139            } else {
140                log.debug( "didn't add page because it doesn't exist: " + page );
141            }
142        }
143
144        session.setAttribute(BREADCRUMBTRAIL_KEY, trail);
145
146        //
147        //  Print out the breadcrumb trail
148        //
149
150        // FIXME: this code would be much simpler if we could just output the [pagename] and then use the
151        // wiki engine to output the appropriate wikilink
152
153        final JspWriter out     = pageContext.getOut();
154        final int queueSize     = trail.size();
155        final String linkclass  = "wikipage";
156        String curPage    = null;
157
158        for( int i = 0; i < queueSize - 1; i++ ) {
159            curPage = trail.get(i);
160
161            //FIXME: I can't figure out how to detect the appropriate jsp page to put here, so I hard coded Wiki.jsp
162            //This breaks when you view an attachment metadata page
163            out.print( "<a class=\"" + linkclass + "\" href=\"" + m_wikiContext.getViewURL(curPage) + "\">" +
164                       TextUtil.replaceEntities( curPage ) +
165                       "</a>" );
166
167            if( i < queueSize - 2 ) {
168                out.print(m_separator);
169            }
170        }
171
172        return SKIP_BODY;
173    }
174
175    /**
176     * Extends the LinkedList class to provide a fixed-size queue implementation
177     */
178    public static class FixedQueue extends LinkedList< String > implements Serializable {
179        private int m_size;
180        private static final long serialVersionUID = 0L;
181
182        FixedQueue( final int size ) {
183            m_size = size;
184        }
185
186        String pushItem( final String o ) {
187            add( o );
188            if( size() > m_size ) {
189                return removeFirst();
190            }
191
192            return null;
193        }
194
195        /**
196         * @param pageName the page to be deleted from the breadcrumb
197         */
198        public void removeItem( final String pageName ) {
199            for( int i = 0; i < size(); i++ ) {
200                final String page = get( i );
201                if( page != null && page.equals( pageName ) ) {
202                    remove( page );
203                }
204            }
205        }
206
207    }
208}
209