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 */
019 package org.apache.wiki.plugin;
020
021 import java.io.IOException;
022 import java.text.Collator;
023 import java.text.ParseException;
024 import java.text.RuleBasedCollator;
025 import java.text.SimpleDateFormat;
026 import java.util.ArrayList;
027 import java.util.Collection;
028 import java.util.Date;
029 import java.util.Iterator;
030 import java.util.Map;
031
032 import org.apache.commons.lang.StringUtils;
033 import org.apache.log4j.Logger;
034 import org.apache.oro.text.GlobCompiler;
035 import org.apache.oro.text.regex.MalformedPatternException;
036 import org.apache.oro.text.regex.Pattern;
037 import org.apache.oro.text.regex.PatternCompiler;
038 import org.apache.oro.text.regex.PatternMatcher;
039 import org.apache.oro.text.regex.Perl5Matcher;
040 import org.apache.wiki.PageSorter;
041 import org.apache.wiki.StringTransmutator;
042 import org.apache.wiki.WikiContext;
043 import org.apache.wiki.WikiEngine;
044 import org.apache.wiki.WikiPage;
045 import org.apache.wiki.api.exceptions.PluginException;
046 import org.apache.wiki.api.plugin.WikiPlugin;
047 import org.apache.wiki.parser.MarkupParser;
048 import org.apache.wiki.parser.WikiDocument;
049 import org.apache.wiki.preferences.Preferences;
050 import org.apache.wiki.preferences.Preferences.TimeFormat;
051 import org.apache.wiki.render.RenderingManager;
052 import org.apache.wiki.util.TextUtil;
053 import org.apache.wiki.util.comparators.CollatorComparator;
054 import org.apache.wiki.util.comparators.HumanComparator;
055 import org.apache.wiki.util.comparators.JavaNaturalComparator;
056 import org.apache.wiki.util.comparators.LocaleComparator;
057
058 /**
059 * This is a base class for all plugins using referral things.
060 *
061 * <p>Parameters (also valid for all subclasses of this class) : </p>
062 * <ul>
063 * <li><b>maxwidth</b> - maximum width of generated links</li>
064 * <li><b>separator</b> - separator between generated links (wikitext)</li>
065 * <li><b>after</b> - output after the link</li>
066 * <li><b>before</b> - output before the link</li>
067 * <li><b>exclude</b> - a regular expression of pages to exclude from the list. </li>
068 * <li><b>include</b> - a regular expression of pages to include in the list. </li>
069 * <li><b>show</b> - value is either "pages" (default) or "count". When "count" is specified, shows only the count
070 * of pages which match. (since 2.8)</li>
071 * <li><b>showLastModified</b> - When show=count, shows also the last modified date. (since 2.8)</li>
072 * <li><b>sortOrder</b> - specifies the sort order for the resulting list. Options are
073 * 'human', 'java', 'locale' or a <code>RuleBasedCollator</code> rule string. (since 2.8.3)</li>
074 * </ul>
075 *
076 */
077 public abstract class AbstractReferralPlugin implements WikiPlugin
078 {
079 private static Logger log = Logger.getLogger( AbstractReferralPlugin.class );
080
081 /** Magic value for rendering all items. */
082 public static final int ALL_ITEMS = -1;
083
084 /** Parameter name for setting the maximum width. Value is <tt>{@value}</tt>. */
085 public static final String PARAM_MAXWIDTH = "maxwidth";
086
087 /** Parameter name for the separator string. Value is <tt>{@value}</tt>. */
088 public static final String PARAM_SEPARATOR = "separator";
089
090 /** Parameter name for the output after the link. Value is <tt>{@value}</tt>. */
091 public static final String PARAM_AFTER = "after";
092
093 /** Parameter name for the output before the link. Value is <tt>{@value}</tt>. */
094 public static final String PARAM_BEFORE = "before";
095
096 /** Parameter name for setting the list of excluded patterns. Value is <tt>{@value}</tt>. */
097 public static final String PARAM_EXCLUDE = "exclude";
098
099 /** Parameter name for setting the list of included patterns. Value is <tt>{@value}</tt>. */
100 public static final String PARAM_INCLUDE = "include";
101
102 /** Parameter name for the show parameter. Value is <tt>{@value}</tt>. */
103 public static final String PARAM_SHOW = "show";
104
105 /** Parameter name for setting show to "pages". Value is <tt>{@value}</tt>. */
106 public static final String PARAM_SHOW_VALUE_PAGES = "pages";
107
108 /** Parameter name for setting show to "count". Value is <tt>{@value}</tt>. */
109 public static final String PARAM_SHOW_VALUE_COUNT = "count";
110
111 /** Parameter name for showing the last modification count. Value is <tt>{@value}</tt>. */
112 public static final String PARAM_LASTMODIFIED = "showLastModified";
113
114 /** Parameter name for specifying the sort order. Value is <tt>{@value}</tt>. */
115 protected static final String PARAM_SORTORDER = "sortOrder";
116 protected static final String PARAM_SORTORDER_HUMAN = "human";
117 protected static final String PARAM_SORTORDER_JAVA = "java";
118 protected static final String PARAM_SORTORDER_LOCALE = "locale";
119
120 protected int m_maxwidth = Integer.MAX_VALUE;
121 protected String m_before = ""; // null not blank
122 protected String m_separator = ""; // null not blank
123 protected String m_after = "\\\\";
124
125 protected Pattern[] m_exclude;
126 protected Pattern[] m_include;
127 protected PageSorter m_sorter;
128
129 protected String m_show = "pages";
130 protected boolean m_lastModified=false;
131 // the last modified date of the page that has been last modified:
132 protected Date m_dateLastModified = new Date(0);
133 protected SimpleDateFormat m_dateFormat;
134
135 protected WikiEngine m_engine;
136
137 /**
138 * @param context the wiki context
139 * @param params parameters for initializing the plugin
140 * @throws PluginException if any of the plugin parameters are malformed
141 */
142 // FIXME: The compiled pattern strings should really be cached somehow.
143 public void initialize( WikiContext context, Map<String, String> params )
144 throws PluginException
145 {
146 m_dateFormat = Preferences.getDateFormat( context, TimeFormat.DATETIME );
147 m_engine = context.getEngine();
148 m_maxwidth = TextUtil.parseIntParameter( params.get( PARAM_MAXWIDTH ), Integer.MAX_VALUE );
149 if( m_maxwidth < 0 ) m_maxwidth = 0;
150
151 String s = params.get( PARAM_SEPARATOR );
152
153 if( s != null )
154 {
155 m_separator = s;
156 // pre-2.1.145 there was a separator at the end of the list
157 // if they set the parameters, we use the new format of
158 // before Item1 after separator before Item2 after separator before Item3 after
159 m_after = "";
160 }
161
162 s = params.get( PARAM_BEFORE );
163
164 if( s != null )
165 {
166 m_before = s;
167 }
168
169 s = params.get( PARAM_AFTER );
170
171 if( s != null )
172 {
173 m_after = s;
174 }
175
176 s = params.get( PARAM_EXCLUDE );
177
178 if( s != null )
179 {
180 try
181 {
182 PatternCompiler pc = new GlobCompiler();
183
184 String[] ptrns = StringUtils.split( s, "," );
185
186 m_exclude = new Pattern[ptrns.length];
187
188 for( int i = 0; i < ptrns.length; i++ )
189 {
190 m_exclude[i] = pc.compile( ptrns[i] );
191 }
192 }
193 catch( MalformedPatternException e )
194 {
195 throw new PluginException("Exclude-parameter has a malformed pattern: "+e.getMessage());
196 }
197 }
198
199 // TODO: Cut-n-paste, refactor
200 s = params.get( PARAM_INCLUDE );
201
202 if( s != null )
203 {
204 try
205 {
206 PatternCompiler pc = new GlobCompiler();
207
208 String[] ptrns = StringUtils.split( s, "," );
209
210 m_include = new Pattern[ptrns.length];
211
212 for( int i = 0; i < ptrns.length; i++ )
213 {
214 m_include[i] = pc.compile( ptrns[i] );
215 }
216 }
217 catch( MalformedPatternException e )
218 {
219 throw new PluginException("Include-parameter has a malformed pattern: "+e.getMessage());
220 }
221 }
222
223 // log.debug( "Requested maximum width is "+m_maxwidth );
224 s = params.get(PARAM_SHOW);
225
226 if( s != null )
227 {
228 if( s.equalsIgnoreCase( "count" ) )
229 {
230 m_show = "count";
231 }
232 }
233
234 s = params.get( PARAM_LASTMODIFIED );
235
236 if( s != null )
237 {
238 if( s.equalsIgnoreCase( "true" ) )
239 {
240 if( m_show.equals( "count" ) )
241 {
242 m_lastModified = true;
243 }
244 else
245 {
246 throw new PluginException( "showLastModified=true is only valid if show=count is also specified" );
247 }
248 }
249 }
250
251 initSorter( context, params );
252 }
253
254 /**
255 * Filters a collection according to the include and exclude parameters.
256 *
257 * @param c The collection to filter.
258 * @return A filtered collection.
259 */
260 protected Collection filterCollection( Collection c )
261 {
262 ArrayList<Object> result = new ArrayList<Object>();
263
264 PatternMatcher pm = new Perl5Matcher();
265
266 for( Iterator i = c.iterator(); i.hasNext(); )
267 {
268 String pageName = null;
269 Object objectje = i.next();
270 if( objectje instanceof WikiPage )
271 {
272 pageName = ((WikiPage) objectje).getName();
273 }
274 else
275 {
276 pageName = (String) objectje;
277 }
278
279 //
280 // If include parameter exists, then by default we include only those
281 // pages in it (excluding the ones in the exclude pattern list).
282 //
283 // include='*' means the same as no include.
284 //
285 boolean includeThis = m_include == null;
286
287 if( m_include != null )
288 {
289 for( int j = 0; j < m_include.length; j++ )
290 {
291 if( pm.matches( pageName, m_include[j] ) )
292 {
293 includeThis = true;
294 break;
295 }
296 }
297 }
298
299 if( m_exclude != null )
300 {
301 for( int j = 0; j < m_exclude.length; j++ )
302 {
303 if( pm.matches( pageName, m_exclude[j] ) )
304 {
305 includeThis = false;
306 break; // The inner loop, continue on the next item
307 }
308 }
309 }
310
311 if( includeThis )
312 {
313 if( objectje instanceof WikiPage )
314 {
315 result.add( objectje );
316 }
317 else
318 {
319 result.add( pageName );
320 }
321 //
322 // if we want to show the last modified date of the most recently change page, we keep a "high watermark" here:
323 WikiPage page = null;
324 if( m_lastModified )
325 {
326 page = m_engine.getPage( pageName );
327 if( page != null )
328 {
329 Date lastModPage = page.getLastModified();
330 if( log.isDebugEnabled() )
331 {
332 log.debug( "lastModified Date of page " + pageName + " : " + m_dateLastModified );
333 }
334 if( lastModPage.after( m_dateLastModified ) )
335 {
336 m_dateLastModified = lastModPage;
337 }
338 }
339
340 }
341 }
342 }
343
344 return result;
345 }
346
347 /**
348 * Filters and sorts a collection according to the include and exclude parameters.
349 *
350 * @param c The collection to filter.
351 * @return A filtered and sorted collection.
352 */
353 @SuppressWarnings( "unchecked" )
354 protected Collection filterAndSortCollection( Collection c )
355 {
356 ArrayList<Object> result = (ArrayList<Object>)filterCollection( c );
357 m_sorter.sortPages( result );
358 return result;
359 }
360
361 /**
362 * Makes WikiText from a Collection.
363 *
364 * @param links Collection to make into WikiText.
365 * @param separator Separator string to use.
366 * @param numItems How many items to show.
367 * @return The WikiText
368 */
369 protected String wikitizeCollection( Collection links, String separator, int numItems )
370 {
371 if( links == null || links.isEmpty() )
372 return "";
373
374 StringBuffer output = new StringBuffer();
375
376 Iterator it = links.iterator();
377 int count = 0;
378
379 //
380 // The output will be B Item[1] A S B Item[2] A S B Item[3] A
381 //
382 while( it.hasNext() && ( (count < numItems) || ( numItems == ALL_ITEMS ) ) )
383 {
384 String value = (String)it.next();
385
386 if( count > 0 )
387 {
388 output.append( m_after );
389 output.append( m_separator );
390 }
391
392 output.append( m_before );
393
394 // Make a Wiki markup link. See TranslatorReader.
395 output.append( "[" + m_engine.beautifyTitle(value) + "|" + value + "]" );
396 count++;
397 }
398
399 //
400 // Output final item - if there have been none, no "after" is printed
401 //
402 if( count > 0 ) output.append( m_after );
403
404 return output.toString();
405 }
406
407 /**
408 * Makes HTML with common parameters.
409 *
410 * @param context The WikiContext
411 * @param wikitext The wikitext to render
412 * @return HTML
413 * @since 1.6.4
414 */
415 protected String makeHTML( WikiContext context, String wikitext )
416 {
417 String result = "";
418
419 RenderingManager mgr = m_engine.getRenderingManager();
420
421 try
422 {
423 MarkupParser parser = mgr.getParser(context, wikitext);
424
425 parser.addLinkTransmutator( new CutMutator(m_maxwidth) );
426 parser.enableImageInlining( false );
427
428 WikiDocument doc = parser.parse();
429
430 result = mgr.getHTML( context, doc );
431 }
432 catch( IOException e )
433 {
434 log.error("Failed to convert page data to HTML", e);
435 }
436
437 return result;
438 }
439
440 /**
441 * A simple class that just cuts a String to a maximum
442 * length, adding three dots after the cutpoint.
443 */
444 private static class CutMutator implements StringTransmutator
445 {
446 private int m_length;
447
448 public CutMutator( int length )
449 {
450 m_length = length;
451 }
452
453 public String mutate( WikiContext context, String text )
454 {
455 if( text.length() > m_length )
456 {
457 return text.substring( 0, m_length ) + "...";
458 }
459
460 return text;
461 }
462 }
463
464 /**
465 * Helper method to initialize the comparator for this page.
466 */
467 private void initSorter( WikiContext context, Map<String, String> params )
468 {
469 String order = params.get( PARAM_SORTORDER );
470 if( order == null || order.length() == 0 )
471 {
472 // Use the configured comparator
473 m_sorter = context.getEngine().getPageSorter();
474 }
475 else if( order.equalsIgnoreCase( PARAM_SORTORDER_JAVA ) )
476 {
477 // use Java "natural" ordering
478 m_sorter = new PageSorter( JavaNaturalComparator.DEFAULT_JAVA_COMPARATOR );
479 }
480 else if( order.equalsIgnoreCase( PARAM_SORTORDER_LOCALE ) )
481 {
482 // use this locale's ordering
483 m_sorter = new PageSorter( LocaleComparator.DEFAULT_LOCALE_COMPARATOR );
484 }
485 else if( order.equalsIgnoreCase( PARAM_SORTORDER_HUMAN ) )
486 {
487 // use human ordering
488 m_sorter = new PageSorter( HumanComparator.DEFAULT_HUMAN_COMPARATOR );
489 }
490 else
491 try
492 {
493 Collator collator = new RuleBasedCollator( order );
494 collator.setStrength( Collator.PRIMARY );
495 m_sorter = new PageSorter( new CollatorComparator( collator ) );
496 }
497 catch( ParseException pe )
498 {
499 log.info( "Failed to parse requested collator - using default ordering", pe );
500 m_sorter = context.getEngine().getPageSorter();
501 }
502 }
503 }