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.plugin;
020
021import java.util.*;
022
023import org.apache.log4j.Logger;
024import org.apache.oro.text.regex.*;
025import org.apache.wiki.ReferenceManager;
026import org.apache.wiki.WikiContext;
027import org.apache.wiki.WikiEngine;
028import org.apache.wiki.WikiPage;
029import org.apache.wiki.api.exceptions.PluginException;
030import org.apache.wiki.api.plugin.WikiPlugin;
031import org.apache.wiki.util.TextUtil;
032
033
034/**
035 *  Displays the pages referring to the current page.
036 *
037 *  <p>Parameters</p>
038 *  <ul>
039 *    <li><b>name</b> - Name of the root page. Default name of calling page
040 *    <li><b>type</b> - local|externalattachment
041 *    <li><b>depth</b> - How many levels of pages to be parsed.
042 *    <li><b>include</b> - Include only these pages. (eg. include='UC.*|BP.*' )
043 *    <li><b>exclude</b> - Exclude with this pattern. (eg. exclude='LeftMenu' )
044 *    <li><b>format</b> -  full|compact, FULL now expands all levels correctly
045 *  </ul>
046 *
047 */
048public class ReferredPagesPlugin implements WikiPlugin
049{
050    private static Logger log = Logger.getLogger( ReferredPagesPlugin.class );
051    private WikiEngine     m_engine;
052    private int            m_depth;
053    private HashSet<String> m_exists  = new HashSet<String>();
054    private StringBuffer   m_result  = new StringBuffer(1024);
055    private PatternMatcher m_matcher = new Perl5Matcher();
056    private Pattern        m_includePattern;
057    private Pattern        m_excludePattern;
058    private boolean m_formatCompact  = true;
059    private boolean m_formatSort     = false;
060
061    /** The parameter name for the root page to start from.  Value is <tt>{@value}</tt>. */
062    public static final String PARAM_ROOT    = "page";
063
064    /** The parameter name for the depth.  Value is <tt>{@value}</tt>. */
065    public static final String PARAM_DEPTH   = "depth";
066
067    /** The parameter name for the type of the references.  Value is <tt>{@value}</tt>. */
068    public static final String PARAM_TYPE    = "type";
069    
070    /** The parameter name for the included pages.  Value is <tt>{@value}</tt>. */
071    public static final String PARAM_INCLUDE = "include";
072    
073    /** The parameter name for the excluded pages.  Value is <tt>{@value}</tt>. */
074    public static final String PARAM_EXCLUDE = "exclude";
075    
076    /** The parameter name for the format.  Value is <tt>{@value}</tt>. */
077    public static final String PARAM_FORMAT  = "format";
078    
079    /** The minimum depth. Value is <tt>{@value}</tt>. */
080    public static final int    MIN_DEPTH = 1;
081    
082    /** The maximum depth. Value is <tt>{@value}</tt>. */
083    public static final int    MAX_DEPTH = 8;
084
085    /**
086     *  {@inheritDoc}
087     */
088    public String execute( WikiContext context, Map<String, String> params )
089        throws PluginException
090    {
091        m_engine = context.getEngine();
092
093        WikiPage         page   = context.getPage();
094        if( page == null ) return "";
095
096        // parse parameters
097        String rootname = params.get( PARAM_ROOT );
098        if( rootname == null ) rootname = page.getName() ;
099
100        String format = params.get( PARAM_FORMAT );
101        if( format == null) format = "";
102        if( format.indexOf( "full" ) >=0 ) m_formatCompact = false ;
103        if( format.indexOf( "sort" ) >=0 ) m_formatSort    = true  ;
104
105        m_depth = TextUtil.parseIntParameter( params.get( PARAM_DEPTH ), MIN_DEPTH );
106        if( m_depth > MAX_DEPTH )  m_depth = MAX_DEPTH;
107
108        String includePattern = params.get(PARAM_INCLUDE);
109        if( includePattern == null ) includePattern = ".*";
110
111        String excludePattern = params.get(PARAM_EXCLUDE);
112        if( excludePattern == null ) excludePattern = "^$";
113
114        log.debug( "Fetching referred pages for "+ rootname +
115                   " with a depth of "+ m_depth +
116                   " with include pattern of "+ includePattern +
117                   " with exclude pattern of "+ excludePattern );
118
119        //
120        // do the actual work
121        //
122        String href  = context.getViewURL(rootname);
123        String title = "ReferredPagesPlugin: depth["+m_depth+
124                       "] include["+includePattern+"] exclude["+excludePattern+
125                       "] format["+(m_formatCompact ? "compact" : "full") +
126                       (m_formatSort ? " sort" : "") + "]";
127
128        m_result.append("<div class=\"ReferredPagesPlugin\">\n");
129        m_result.append("<a class=\"wikipage\" href=\""+ href +
130                        "\" title=\"" + title +
131                        "\">" + rootname + "</a>\n");
132        m_exists.add(rootname);
133
134        // pre compile all needed patterns
135        // glob compiler :  * is 0..n instance of any char  -- more convenient as input
136        // perl5 compiler : .* is 0..n instances of any char -- more powerful
137        //PatternCompiler g_compiler = new GlobCompiler();
138        PatternCompiler compiler = new Perl5Compiler();
139
140        try
141        {
142            m_includePattern = compiler.compile(includePattern);
143
144            m_excludePattern = compiler.compile(excludePattern);
145        }
146        catch( MalformedPatternException e )
147        {
148            if (m_includePattern == null )
149            {
150                throw new PluginException("Illegal include pattern detected.");
151            }
152            else if (m_excludePattern == null )
153            {
154                throw new PluginException("Illegal exclude pattern detected.");
155            }
156            else
157            {
158                throw new PluginException("Illegal internal pattern detected.");
159            }
160        }
161
162        // go get all referred links
163        getReferredPages(context,rootname, 0);
164
165        // close and finish
166        m_result.append ("</div>\n" ) ;
167
168        return m_result.toString() ;
169    }
170
171
172    /**
173     * Retrieves a list of all referred pages. Is called recursively
174     * depending on the depth parameter
175     */
176    @SuppressWarnings("unchecked")
177    private void getReferredPages( WikiContext context, String pagename, int depth )
178    {
179        if( depth >= m_depth ) return;  // end of recursion
180        if( pagename == null ) return;
181        if( !m_engine.pageExists(pagename) ) return;
182
183        ReferenceManager mgr = m_engine.getReferenceManager();
184
185        Collection<String> allPages = mgr.findRefersTo( pagename );
186
187        handleLinks( context, allPages, ++depth, pagename );
188    }
189
190    private void handleLinks(WikiContext context,Collection<String> links, int depth, String pagename)
191    {
192        boolean isUL = false;
193        HashSet<String> localLinkSet = new HashSet<String>();  // needed to skip multiple
194        // links to the same page
195        localLinkSet.add(pagename);
196
197        ArrayList<String> allLinks = new ArrayList<String>();
198
199        if( links != null )
200            allLinks.addAll( links );
201
202        if( m_formatSort ) context.getEngine().getPageSorter().sort( allLinks );
203
204        for( Iterator i = allLinks.iterator(); i.hasNext(); )
205        {
206            String link = (String) i.next() ;
207
208            if( localLinkSet.contains( link ) ) continue; // skip multiple
209                                                          // links to the same
210                                                          // page
211            localLinkSet.add( link );
212
213            if( !m_engine.pageExists( link ) ) continue; // hide links to non
214                                                         // existing pages
215
216            if(  m_matcher.matches( link , m_excludePattern ) ) continue;
217            if( !m_matcher.matches( link , m_includePattern ) ) continue;
218
219            if( m_exists.contains( link ) )
220            {
221                if( !m_formatCompact )
222                {
223                    if( !isUL )
224                    {
225                        isUL = true; m_result.append("<ul>\n");
226                    }
227
228                    m_result.append("<li> " + link + " </li>\n");
229
230                    getReferredPages( context, link, depth );  // added recursive
231                                                      // call - on general
232                                                      // request
233                }
234            }
235            else
236            {
237                if( !isUL )
238                {
239                    isUL = true; m_result.append("<ul>\n");
240                }
241
242                String href = context.getURL(WikiContext.VIEW,link);
243                m_result.append("<li><a class=\"wikipage\" href=\""+ href +"\">"+link+"</a></li>\n" );
244
245                m_exists.add( link );
246
247                getReferredPages( context, link, depth );
248            }
249        }
250
251        if( isUL ) m_result.append("</ul>\n");
252    }
253}
254