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.wiki.WikiContext;
022
023import javax.servlet.http.HttpSession;
024import javax.servlet.jsp.JspWriter;
025
026import org.apache.log4j.Logger;
027
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(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(String separator)
105    {
106        m_separator = separator;
107    }
108
109    /**
110     *  {@inheritDoc}
111     */
112    @Override
113    public int doWikiStartTag() throws IOException
114    {
115        HttpSession session = pageContext.getSession();
116        FixedQueue  trail = (FixedQueue) session.getAttribute(BREADCRUMBTRAIL_KEY);
117
118        String page = m_wikiContext.getPage().getName();
119
120        if( trail == null )
121        {
122            trail = new FixedQueue(m_maxQueueSize);
123        } else {
124            //  check if page still exists (could be deleted/renamed by another user)
125            for (int i = 0;i<trail.size();i++) {
126                if (!m_wikiContext.getEngine().pageExists(trail.get(i))) {
127                    trail.remove(i);
128                }
129            }
130        }
131
132        if (m_wikiContext.getRequestContext().equals(WikiContext.VIEW))
133        {
134            if (m_wikiContext.getEngine().pageExists(page))
135            {
136                if (trail.isEmpty())
137                {
138                    trail.pushItem(page);
139                }
140                else
141                {
142                    //
143                    // Don't add the page to the queue if the page was just refreshed
144                    //
145                    if (!trail.getLast().equals(page))
146                    {
147                        trail.pushItem(page);
148                    }
149                }
150            }
151            else
152            {
153                log.debug("didn't add page because it doesn't exist: " + page);
154            }
155        }
156
157        session.setAttribute(BREADCRUMBTRAIL_KEY, trail);
158
159        //
160        //  Print out the breadcrumb trail
161        //
162
163        // FIXME: this code would be much simpler if we could just output the [pagename] and then use the
164        // wiki engine to output the appropriate wikilink
165
166        JspWriter out     = pageContext.getOut();
167        int queueSize     = trail.size();
168        String linkclass  = "wikipage";
169        String curPage    = null;
170
171        for( int i = 0; i < queueSize - 1; i++ )
172        {
173            curPage = trail.get(i);
174
175            //FIXME: I can't figure out how to detect the appropriate jsp page to put here, so I hard coded Wiki.jsp
176            //This breaks when you view an attachment metadata page
177            out.print("<a class=\"" + linkclass + "\" href=\"" + m_wikiContext.getViewURL(curPage)+ "\">" + curPage + "</a>");
178
179            if( i < queueSize - 2 )
180            {
181                out.print(m_separator);
182            }
183        }
184
185        return SKIP_BODY;
186    }
187
188    /**
189     * Extends the LinkedList class to provide a fixed-size queue implementation
190     */
191    public static class FixedQueue
192        extends LinkedList<String>
193        implements Serializable
194    {
195        private int m_size;
196        private static final long serialVersionUID = 0L;
197
198        FixedQueue(int size)
199        {
200            m_size = size;
201        }
202
203        String pushItem(String o)
204        {
205            add(o);
206            if( size() > m_size )
207            {
208                return removeFirst();
209            }
210
211            return null;
212        }
213        
214        /**
215         * @param pageName
216         *            the page to be deleted from the breadcrumb
217         */
218        public void removeItem(String pageName)
219        {
220            for (int i = 0; i < size(); i++)
221            {
222                String page = get(i);
223                if (page != null && page.equals(pageName))
224                {
225                    remove(page);
226                }
227            }
228        }
229
230    }
231}
232