You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jspwiki.apache.org by aj...@apache.org on 2008/02/13 06:54:24 UTC

svn commit: r627255 [32/41] - in /incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src: ./ com/ com/ecyrd/ com/ecyrd/jspwiki/ com/ecyrd/jspwiki/action/ com/ecyrd/jspwiki/attachment/ com/ecyrd/jspwiki/auth/ com/ecyrd/jspwiki/auth/acl/ com/ecyrd/jspwiki...

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/search/LuceneSearchProvider.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/search/LuceneSearchProvider.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/search/LuceneSearchProvider.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/search/LuceneSearchProvider.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,760 @@
+/*
+JSPWiki - a JSP-based WikiWiki clone.
+
+Copyright (C) 2005 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+package com.ecyrd.jspwiki.search;
+
+import java.io.*;
+import java.util.*;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.log4j.Logger;
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.analysis.TokenStream;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.queryParser.MultiFieldQueryParser;
+import org.apache.lucene.queryParser.ParseException;
+import org.apache.lucene.queryParser.QueryParser;
+import org.apache.lucene.search.Hits;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.Searcher;
+import org.apache.lucene.search.highlight.Highlighter;
+import org.apache.lucene.search.highlight.QueryScorer;
+import org.apache.lucene.search.highlight.SimpleHTMLEncoder;
+import org.apache.lucene.search.highlight.SimpleHTMLFormatter;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.FSDirectory;
+
+import com.ecyrd.jspwiki.*;
+import com.ecyrd.jspwiki.attachment.Attachment;
+import com.ecyrd.jspwiki.attachment.AttachmentManager;
+import com.ecyrd.jspwiki.parser.MarkupParser;
+import com.ecyrd.jspwiki.providers.ProviderException;
+import com.ecyrd.jspwiki.providers.WikiPageProvider;
+import com.ecyrd.jspwiki.util.ClassUtil;
+import com.ecyrd.jspwiki.util.WatchDog;
+import com.ecyrd.jspwiki.util.WikiBackgroundThread;
+
+/**
+ *  Interface for the search providers that handle searching the Wiki
+ *
+ *  @author Arent-Jan Banck
+ *  @since 2.2.21.
+ */
+public class LuceneSearchProvider implements SearchProvider
+{
+    protected static final Logger log = Logger.getLogger(LuceneSearchProvider.class);
+
+    private WikiEngine m_engine;
+
+    // Lucene properties.
+
+    /** Which analyzer to use.  Default is StandardAnalyzer. */
+    public static final String PROP_LUCENE_ANALYZER    = "jspwiki.lucene.analyzer";
+
+    private static final String PROP_LUCENE_INDEXDELAY   = "jspwiki.lucene.indexdelay";
+    private static final String PROP_LUCENE_INITIALDELAY = "jspwiki.lucene.initialdelay";
+
+    private String m_analyzerClass = "org.apache.lucene.analysis.standard.StandardAnalyzer";
+
+    private static final String LUCENE_DIR             = "lucene";
+
+    /**
+     *  Number of page updates before we optimize the index.
+     */
+    public static final int LUCENE_OPTIMIZE_COUNT      = 10;
+    protected static final String LUCENE_ID            = "id";
+    protected static final String LUCENE_PAGE_CONTENTS = "contents";
+    protected static final String LUCENE_AUTHOR        = "author";
+    protected static final String LUCENE_ATTACHMENTS   = "attachment";
+    protected static final String LUCENE_PAGE_NAME     = "name";
+
+    private String           m_luceneDirectory = null;
+    private int              m_updateCount = 0;
+    protected Vector<Object[]> m_updates = new Vector<Object[]>(); // Vector because multi-threaded.
+
+    /** Maximum number of fragments from search matches. */
+    private static final int MAX_FRAGMENTS = 3;
+
+    private static String c_punctuationSpaces = StringUtils.repeat(" ", MarkupParser.PUNCTUATION_CHARS_ALLOWED.length() );
+
+    /**
+     *  {@inheritDoc}
+     */
+    public void initialize(WikiEngine engine, Properties props)
+            throws NoRequiredPropertyException, IOException
+    {
+        m_engine = engine;
+
+        m_luceneDirectory = engine.getWorkDir()+File.separator+LUCENE_DIR;
+
+        int initialDelay = TextUtil.getIntegerProperty( props, PROP_LUCENE_INITIALDELAY, LuceneUpdater.INITIAL_DELAY );
+        int indexDelay   = TextUtil.getIntegerProperty( props, PROP_LUCENE_INDEXDELAY, LuceneUpdater.INDEX_DELAY );
+
+        m_analyzerClass = TextUtil.getStringProperty( props, PROP_LUCENE_ANALYZER, m_analyzerClass );
+        // FIXME: Just to be simple for now, we will do full reindex
+        // only if no files are in lucene directory.
+
+        File dir = new File(m_luceneDirectory);
+
+        log.info("Lucene enabled, cache will be in: "+dir.getAbsolutePath());
+
+        try
+        {
+            if( !dir.exists() )
+            {
+                dir.mkdirs();
+            }
+
+            if( !dir.exists() || !dir.canWrite() || !dir.canRead() )
+            {
+                log.error("Cannot write to Lucene directory, disabling Lucene: "+dir.getAbsolutePath());
+                throw new IOException( "Invalid Lucene directory." );
+            }
+
+            String[] filelist = dir.list();
+
+            if( filelist == null )
+            {
+                throw new IOException( "Invalid Lucene directory: cannot produce listing: "+dir.getAbsolutePath());
+            }
+        }
+        catch ( IOException e )
+        {
+            log.error("Problem while creating Lucene index - not using Lucene.", e);
+        }
+
+        // Start the Lucene update thread, which waits first
+        // for a little while before starting to go through
+        // the Lucene "pages that need updating".
+        LuceneUpdater updater = new LuceneUpdater( m_engine, this, initialDelay, indexDelay );
+        updater.start();
+    }
+
+    /**
+     *  Returns the handling engine.
+     *
+     *  @return Current WikiEngine
+     */
+    protected WikiEngine getEngine()
+    {
+        return m_engine;
+    }
+
+    /**
+     *  Performs a full Lucene reindex, if necessary.
+     *
+     *  @throws IOException If there's a problem during indexing
+     */
+    protected void doFullLuceneReindex()
+        throws IOException
+    {
+        File dir = new File(m_luceneDirectory);
+
+        String[] filelist = dir.list();
+
+        if( filelist == null )
+        {
+            throw new IOException( "Invalid Lucene directory: cannot produce listing: "+dir.getAbsolutePath());
+        }
+
+        try
+        {
+            if( filelist.length == 0 )
+            {
+                //
+                //  No files? Reindex!
+                //
+                Date start = new Date();
+                IndexWriter writer = null;
+
+                log.info("Starting Lucene reindexing, this can take a couple minutes...");
+
+                //
+                //  Do lock recovery, in case JSPWiki was shut down forcibly
+                //
+                Directory luceneDir = FSDirectory.getDirectory(dir,false);
+
+                if( IndexReader.isLocked(luceneDir) )
+                {
+                    log.info("JSPWiki was shut down while Lucene was indexing - unlocking now.");
+                    IndexReader.unlock( luceneDir );
+                }
+
+                try
+                {
+                    writer = new IndexWriter( m_luceneDirectory,
+                                              getLuceneAnalyzer(),
+                                              true );
+                    Collection<WikiPage> allPages = m_engine.getPageManager().getAllPages();
+
+                    for( WikiPage page : allPages )
+                    {
+                        String text = m_engine.getPageManager().getPageText( page.getName(),
+                                                                             WikiProvider.LATEST_VERSION );
+                        luceneIndexPage( page, text, writer );
+                    }
+
+                    Collection<Attachment> allAttachments = m_engine.getAttachmentManager().getAllAttachments();
+                    for( Attachment att : allAttachments )
+                    {
+                        String text = getAttachmentContent( att.getName(),
+                                                            WikiProvider.LATEST_VERSION );
+                        luceneIndexPage( att, text, writer );
+                    }
+
+                    writer.optimize();
+                }
+                finally
+                {
+                    try
+                    {
+                        if( writer != null ) writer.close();
+                    }
+                    catch( IOException e ) {}
+                }
+
+                Date end = new Date();
+                log.info("Full Lucene index finished in " +
+                         (end.getTime() - start.getTime()) + " milliseconds.");
+            }
+            else
+            {
+                log.info("Files found in Lucene directory, not reindexing.");
+            }
+        }
+        catch( NoClassDefFoundError e )
+        {
+            log.info("Lucene libraries do not exist - not using Lucene.");
+        }
+        catch ( IOException e )
+        {
+            log.error("Problem while creating Lucene index - not using Lucene.", e);
+        }
+        catch ( ProviderException e )
+        {
+            log.error("Problem reading pages while creating Lucene index (JSPWiki won't start.)", e);
+            throw new IllegalArgumentException("unable to create Lucene index");
+        }
+        catch( ClassNotFoundException e )
+        {
+            log.error("Illegal Analyzer specified:",e);
+        }
+        catch( Exception e )
+        {
+            log.error("Unable to start lucene",e);
+        }
+
+    }
+
+    /**
+     *  Fetches the attachment content from the repository.
+     *  Content is flat text that can be used for indexing/searching or display
+     */
+    protected String getAttachmentContent( String attachmentName, int version )
+    {
+        AttachmentManager mgr = m_engine.getAttachmentManager();
+
+        try
+        {
+            Attachment att = mgr.getAttachmentInfo( attachmentName, version );
+            //FIXME: Find out why sometimes att is null
+            if(att != null)
+            {
+                return getAttachmentContent( att );
+            }
+        }
+        catch (ProviderException e)
+        {
+            log.error("Attachment cannot be loaded", e);
+        }
+        // Something was wrong, no result is returned.
+        return null;
+    }
+
+    /**
+     * @param att Attachment to get content for. Filename extension is used to determine the type of the attachment.
+     * @return String representing the content of the file.
+     * FIXME This is a very simple implementation of some text-based attachment, mainly used for testing.
+     * This should be replaced /moved to Attachment search providers or some other 'plugable' wat to search attachments
+     */
+    protected String getAttachmentContent( Attachment att )
+    {
+        AttachmentManager mgr = m_engine.getAttachmentManager();
+        //FIXME: Add attachment plugin structure
+
+        String filename = att.getFileName();
+
+        if(filename.endsWith(".txt") ||
+           filename.endsWith(".xml") ||
+           filename.endsWith(".ini") ||
+           filename.endsWith(".html"))
+        {
+            InputStream attStream;
+
+            try
+            {
+                attStream = mgr.getAttachmentStream( att );
+
+                StringWriter sout = new StringWriter();
+                FileUtil.copyContents( new InputStreamReader(attStream), sout );
+
+                attStream.close();
+                sout.close();
+
+                return sout.toString();
+            }
+            catch (ProviderException e)
+            {
+                log.error("Attachment cannot be loaded", e);
+                return null;
+            }
+            catch (IOException e)
+            {
+                log.error("Attachment cannot be loaded", e);
+                return null;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     *  Updates the lucene index for a single page.
+     *
+     *  @param page The WikiPage to check
+     *  @param text The page text to index.
+     */
+    protected synchronized void updateLuceneIndex( WikiPage page, String text )
+    {
+        IndexWriter writer = null;
+
+        log.debug("Updating Lucene index for page '" + page.getName() + "'...");
+
+        try
+        {
+            pageRemoved(page);
+
+            // Now add back the new version.
+            writer = new IndexWriter(m_luceneDirectory, getLuceneAnalyzer(), false);
+            luceneIndexPage(page, text, writer);
+            m_updateCount++;
+            if( m_updateCount >= LUCENE_OPTIMIZE_COUNT )
+            {
+                writer.optimize();
+                m_updateCount = 0;
+            }
+        }
+        catch ( IOException e )
+        {
+            log.error("Unable to update page '" + page.getName() + "' from Lucene index", e);
+        }
+        catch( Exception e )
+        {
+            log.error("Unexpected Lucene exception - please check configuration!",e);
+        }
+        finally
+        {
+            try
+            {
+                if( writer != null ) writer.close();
+            }
+            catch( IOException e ) {}
+        }
+
+        log.debug("Done updating Lucene index for page '" + page.getName() + "'.");
+    }
+
+
+    private Analyzer getLuceneAnalyzer()
+        throws ClassNotFoundException,
+               InstantiationException,
+               IllegalAccessException
+    {
+        Class clazz = ClassUtil.findClass( "", m_analyzerClass );
+        Analyzer analyzer = (Analyzer)clazz.newInstance();
+        return analyzer;
+    }
+
+    /**
+     *  Indexes page using the given IndexWriter.
+     *
+     *  @param page WikiPage
+     *  @param text Page text to index
+     *  @param writer The Lucene IndexWriter to use for indexing
+     *  @return the created index Document
+     *  @throws IOException If there's an indexing problem
+     */
+    protected Document luceneIndexPage( WikiPage page, String text, IndexWriter writer )
+        throws IOException
+    {
+        // make a new, empty document
+        Document doc = new Document();
+
+        if( text == null ) return doc;
+
+        // Raw name is the keyword we'll use to refer to this document for updates.
+        Field field = new Field(LUCENE_ID, page.getName(), Field.Store.YES, Field.Index.UN_TOKENIZED);
+        doc.add( field );
+
+        // Body text.  It is stored in the doc for search contexts.
+        field = new Field(LUCENE_PAGE_CONTENTS, text,
+                          Field.Store.YES, Field.Index.TOKENIZED, Field.TermVector.NO);
+        doc.add( field );
+
+        // Allow searching by page name. Both beautified and raw
+        String unTokenizedTitle = StringUtils.replaceChars( page.getName(),
+                                                            MarkupParser.PUNCTUATION_CHARS_ALLOWED,
+                                                            c_punctuationSpaces );
+
+        field = new Field(LUCENE_PAGE_NAME,
+                          TextUtil.beautifyString( page.getName() ) + " " + unTokenizedTitle,
+                          Field.Store.YES, Field.Index.TOKENIZED, Field.TermVector.NO);
+        doc.add( field );
+
+        // Allow searching by authorname
+
+        if( page.getAuthor() != null )
+        {
+            field = new Field(LUCENE_AUTHOR, page.getAuthor(),
+                              Field.Store.YES, Field.Index.TOKENIZED, Field.TermVector.NO);
+            doc.add( field );
+        }
+
+        // Now add the names of the attachments of this page
+        try
+        {
+            Collection<Attachment> attachments = m_engine.getAttachmentManager().listAttachments(page);
+            String attachmentNames = "";
+
+            for( Attachment att : attachments )
+            {
+                attachmentNames += att.getName() + ";";
+            }
+            field = new Field(LUCENE_ATTACHMENTS, attachmentNames,
+                              Field.Store.YES, Field.Index.TOKENIZED, Field.TermVector.NO);
+            doc.add( field );
+
+        }
+        catch(ProviderException e)
+        {
+            // Unable to read attachments
+            log.error("Failed to get attachments for page", e);
+        }
+        writer.addDocument(doc);
+
+        return doc;
+    }
+
+    /**
+     *  {@inheritDoc}
+     */
+    public void pageRemoved( WikiPage page )
+    {
+        try
+        {
+            // Must first remove existing version of page.
+            IndexReader reader = IndexReader.open(m_luceneDirectory);
+            reader.deleteDocuments(new Term(LUCENE_ID, page.getName()));
+            reader.close();
+        }
+        catch ( IOException e )
+        {
+            log.error("Unable to update page '" + page.getName() + "' from Lucene index", e);
+        }
+    }
+
+
+    /**
+     *  Adds a page-text pair to the lucene update queue.  Safe to call always
+     *
+     *  @param page WikiPage to add to the update queue.
+     */
+    public void reindexPage( WikiPage page )
+    {
+        if( page != null )
+        {
+            String text;
+
+            // TODO: Think if this was better done in the thread itself?
+
+            if( page instanceof Attachment )
+            {
+                text = getAttachmentContent( (Attachment) page );
+            }
+            else
+            {
+                text = m_engine.getPureText( page );
+            }
+
+            if( text != null )
+            {
+                // Add work item to m_updates queue.
+                Object[] pair = new Object[2];
+                pair[0] = page;
+                pair[1] = text;
+                m_updates.add(pair);
+                log.debug("Scheduling page " + page.getName() + " for index update");
+            }
+        }
+    }
+
+    /**
+     *  {@inheritDoc}
+     */
+    public Collection<SearchResult> findPages( String query )
+        throws ProviderException
+    {
+        return findPages( query, FLAG_CONTEXTS );
+    }
+
+    /**
+     *  Create contexts also.  Generating contexts can be expensive,
+     *  so they're not on by default.
+     */
+    public static final int FLAG_CONTEXTS = 0x01;
+
+    /**
+     *  Searches pages using a particular combination of flags.
+     *
+     *  @param query The query to perform in Lucene query language
+     *  @param flags A set of flags
+     *  @return A Collection of SearchResult instances
+     *  @throws ProviderException if there is a problem with the backend
+     */
+    public Collection findPages( String query, int flags )
+        throws ProviderException
+    {
+        Searcher  searcher = null;
+        ArrayList<SearchResult> list     = null;
+        Highlighter highlighter = null;
+
+        try
+        {
+            String[] queryfields = { LUCENE_PAGE_CONTENTS, LUCENE_PAGE_NAME, LUCENE_AUTHOR, LUCENE_ATTACHMENTS };
+            QueryParser qp = new MultiFieldQueryParser( queryfields, getLuceneAnalyzer() );
+
+            //QueryParser qp = new QueryParser( LUCENE_PAGE_CONTENTS, getLuceneAnalyzer() );
+            Query luceneQuery = qp.parse( query );
+
+            if( (flags & FLAG_CONTEXTS) != 0 )
+            {
+                highlighter = new Highlighter(new SimpleHTMLFormatter("<span class=\"searchmatch\">", "</span>"),
+                                              new SimpleHTMLEncoder(),
+                                              new QueryScorer(luceneQuery));
+            }
+
+            try
+            {
+                searcher = new IndexSearcher(m_luceneDirectory);
+            }
+            catch( Exception ex )
+            {
+                log.info("Lucene not yet ready; indexing not started",ex);
+                return null;
+            }
+
+            Hits hits = searcher.search(luceneQuery);
+
+            list = new ArrayList<SearchResult>(hits.length());
+            for ( int curr = 0; curr < hits.length(); curr++ )
+            {
+                Document doc = hits.doc(curr);
+                String pageName = doc.get(LUCENE_ID);
+                WikiPage page = m_engine.getPage(pageName, WikiPageProvider.LATEST_VERSION);
+
+                if(page != null)
+                {
+                    if(page instanceof Attachment)
+                    {
+                        // Currently attachments don't look nice on the search-results page
+                        // When the search-results are cleaned up this can be enabled again.
+                    }
+
+                    int score = (int)(hits.score(curr) * 100);
+
+
+                    // Get highlighted search contexts
+                    String text = doc.get(LUCENE_PAGE_CONTENTS);
+
+                    String[] fragments = new String[0];
+                    if( text != null && highlighter != null )
+                    {
+                        TokenStream tokenStream = getLuceneAnalyzer()
+                        .tokenStream(LUCENE_PAGE_CONTENTS, new StringReader(text));
+                        fragments = highlighter.getBestFragments(tokenStream,
+                                                                 text, MAX_FRAGMENTS);
+
+                    }
+
+                    SearchResult result = new SearchResultImpl( page, score, fragments );                    list.add(result);
+                }
+                else
+                {
+                    log.error("Lucene found a result page '" + pageName + "' that could not be loaded, removing from Lucene cache");
+                    pageRemoved(new WikiPage( m_engine, pageName ));
+                }
+            }
+        }
+        catch( IOException e )
+        {
+            log.error("Failed during lucene search",e);
+        }
+        catch( InstantiationException e )
+        {
+            log.error("Unable to get a Lucene analyzer",e);
+        }
+        catch( IllegalAccessException e )
+        {
+            log.error("Unable to get a Lucene analyzer",e);
+        }
+        catch( ClassNotFoundException e )
+        {
+            log.error("Specified Lucene analyzer does not exist",e);
+        }
+        catch( ParseException e )
+        {
+            log.info("Broken query; cannot parse",e);
+
+            throw new ProviderException("You have entered a query Lucene cannot process: "+e.getMessage());
+        }
+        finally
+        {
+            if( searcher != null )
+            {
+                try
+                {
+                    searcher.close();
+                }
+                catch( IOException e )
+                {}
+            }
+        }
+
+        return list;
+    }
+
+    /**
+     *  {@inheritDoc}
+     */
+    public String getProviderInfo()
+    {
+        return "LuceneSearchProvider";
+    }
+
+    /**
+     * Updater thread that updates Lucene indexes.
+     */
+    private static final class LuceneUpdater extends WikiBackgroundThread
+    {
+        protected static final int INDEX_DELAY    = 1;
+        protected static final int INITIAL_DELAY = 60;
+        private final LuceneSearchProvider m_provider;
+
+        private int m_initialDelay;
+
+        private WatchDog m_watchdog;
+
+        private LuceneUpdater( WikiEngine engine, LuceneSearchProvider provider,
+                               int initialDelay, int indexDelay )
+        {
+            super( engine, indexDelay );
+            m_provider = provider;
+            setName("JSPWiki Lucene Indexer");
+        }
+
+        public void startupTask() throws Exception
+        {
+            m_watchdog = getEngine().getCurrentWatchDog();
+
+            // Sleep initially...
+            try
+            {
+                Thread.sleep( m_initialDelay * 1000L );
+            }
+            catch( InterruptedException e )
+            {
+                throw new InternalWikiException("Interrupted while waiting to start.");
+            }
+
+            m_watchdog.enterState("Full reindex");
+            // Reindex everything
+            m_provider.doFullLuceneReindex();
+            m_watchdog.exitState();
+        }
+
+        public void backgroundTask() throws Exception
+        {
+            m_watchdog.enterState("Emptying index queue", 60);
+
+            synchronized ( m_provider.m_updates )
+            {
+                while( m_provider.m_updates.size() > 0 )
+                {
+                    Object[] pair = m_provider.m_updates.remove(0);
+                    WikiPage page = ( WikiPage ) pair[0];
+                    String text = ( String ) pair[1];
+                    m_provider.updateLuceneIndex(page, text);
+                }
+            }
+
+            m_watchdog.exitState();
+        }
+
+    }
+
+    // FIXME: This class is dumb; needs to have a better implementation
+    private static class SearchResultImpl
+        implements SearchResult
+    {
+        private WikiPage m_page;
+        private int      m_score;
+        private String[] m_contexts;
+
+        public SearchResultImpl( WikiPage page, int score, String[] contexts )
+        {
+            m_page  = page;
+            m_score = score;
+            m_contexts = contexts;
+        }
+
+        public WikiPage getPage()
+        {
+            return m_page;
+        }
+
+        /* (non-Javadoc)
+         * @see com.ecyrd.jspwiki.SearchResult#getScore()
+         */
+        public int getScore()
+        {
+            return m_score;
+        }
+
+
+        public String[] getContexts()
+        {
+            return m_contexts;
+        }
+    }
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/search/SearchManager.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/search/SearchManager.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/search/SearchManager.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/search/SearchManager.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,318 @@
+/*
+JSPWiki - a JSP-based WikiWiki clone.
+
+Copyright (C) 2005 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+package com.ecyrd.jspwiki.search;
+
+import java.io.IOException;
+import java.util.*;
+
+import org.apache.commons.lang.time.StopWatch;
+import org.apache.log4j.Logger;
+
+import com.ecyrd.jspwiki.*;
+import com.ecyrd.jspwiki.event.WikiEvent;
+import com.ecyrd.jspwiki.event.WikiEventListener;
+import com.ecyrd.jspwiki.event.WikiEventUtils;
+import com.ecyrd.jspwiki.event.WikiPageEvent;
+import com.ecyrd.jspwiki.filters.BasicPageFilter;
+import com.ecyrd.jspwiki.filters.FilterException;
+import com.ecyrd.jspwiki.modules.InternalModule;
+import com.ecyrd.jspwiki.parser.MarkupParser;
+import com.ecyrd.jspwiki.providers.ProviderException;
+import com.ecyrd.jspwiki.rpc.RPCCallable;
+import com.ecyrd.jspwiki.rpc.json.JSONRPCManager;
+import com.ecyrd.jspwiki.util.ClassUtil;
+
+/**
+ *  Manages searching the Wiki.
+ *
+ *  @author Arent-Jan Banck
+ *  @since 2.2.21.
+ */
+
+public class SearchManager
+    extends BasicPageFilter
+    implements InternalModule, WikiEventListener
+{
+    private static final Logger log = Logger.getLogger(SearchManager.class);
+
+    private static final String DEFAULT_SEARCHPROVIDER  = "com.ecyrd.jspwiki.search.LuceneSearchProvider";
+    public static final String PROP_USE_LUCENE         = "jspwiki.useLucene";
+    public static final String PROP_SEARCHPROVIDER     = "jspwiki.searchProvider";
+
+    private SearchProvider    m_searchProvider = null;
+
+    public static final String JSON_SEARCH = "search";
+
+    public SearchManager( WikiEngine engine, Properties properties )
+        throws WikiException
+    {
+        initialize( engine, properties );
+
+        WikiEventUtils.addWikiEventListener(m_engine.getPageManager(),
+                                            WikiPageEvent.PAGE_DELETE_REQUEST, this);
+
+        JSONRPCManager.registerGlobalObject( JSON_SEARCH, new JSONSearch() );
+    }
+
+    /**
+     *  Provides a JSON RPC API to the JSPWiki Search Engine.
+     *  @author jalkanen
+     */
+    public class JSONSearch implements RPCCallable
+    {
+        /**
+         *  Provides a list of suggestions to use for a page name.
+         *  Currently the algorithm just looks into the value parameter,
+         *  and returns all page names from that.
+         *
+         *  @param wikiName the page name
+         *  @param maxLength maximum number of suggestions
+         *  @return the suggestions
+         */
+        public List getSuggestions( String wikiName, int maxLength )
+        {
+            StopWatch sw = new StopWatch();
+            sw.start();
+            List<String> list = new ArrayList<String>(maxLength);
+
+            if( wikiName.length() > 0 )
+            {
+                wikiName = MarkupParser.cleanLink(wikiName);
+                wikiName = wikiName.toLowerCase();
+
+                String oldStyleName = MarkupParser.wikifyLink(wikiName).toLowerCase();
+
+                Set<String> allPages = m_engine.getReferenceManager().findCreated();
+
+                int counter = 0;
+                for( Iterator<String> i = allPages.iterator(); i.hasNext() && counter < maxLength; )
+                {
+                    String p = i.next();
+                    String pp = p.toLowerCase();
+                    if( pp.startsWith( wikiName ) || pp.startsWith( oldStyleName ) )
+                    {
+                        list.add( p );
+                        counter++;
+                    }
+                }
+            }
+
+            sw.stop();
+            if( log.isDebugEnabled() ) log.debug("Suggestion request for "+wikiName+" done in "+sw);
+            return list;
+        }
+
+        /**
+         *  Performs a full search of pages.
+         *
+         *  @param searchString The query string
+         *  @param maxLength How many hits to return
+         *  @return the pages found
+         */
+        public List<HashMap<String,Object>> findPages( String searchString, int maxLength )
+        {
+            StopWatch sw = new StopWatch();
+            sw.start();
+
+            List<HashMap<String,Object>> list = new ArrayList<HashMap<String,Object>>(maxLength);
+
+            if( searchString.length() > 0 )
+            {
+                try
+                {
+                    Collection<SearchResult> c;
+
+                    if( m_searchProvider instanceof LuceneSearchProvider )
+                        c = ((LuceneSearchProvider)m_searchProvider).findPages( searchString, 0 );
+                    else
+                        c = m_searchProvider.findPages( searchString );
+
+                    int count = 0;
+                    for( Iterator<SearchResult> i = c.iterator(); i.hasNext() && count < maxLength; count++ )
+                    {
+                        SearchResult sr = i.next();
+                        HashMap<String,Object> hm = new HashMap<String,Object>();
+                        hm.put( "page", sr.getPage().getName() );
+                        hm.put( "score", new Integer(sr.getScore()) );
+                        list.add( hm );
+                    }
+                }
+                catch(Exception e)
+                {
+                    log.info("AJAX search failed; ",e);
+                }
+            }
+
+            sw.stop();
+            if( log.isDebugEnabled() ) log.debug("AJAX search complete in "+sw);
+            return list;
+        }
+    }
+
+    /**
+     *  This particular method starts off indexing and all sorts of various activities,
+     *  so you need to run this last, after things are done.
+     *
+     * @param engine the wiki engine
+     * @param properties the properties used to initialize the wiki engine
+     * @throws FilterException if the search provider failed to initialize
+     */
+    public void initialize(WikiEngine engine, Properties properties)
+        throws FilterException
+    {
+        m_engine = engine;
+
+        loadSearchProvider(properties);
+
+        try
+        {
+            m_searchProvider.initialize(engine, properties);
+        }
+        catch (NoRequiredPropertyException e)
+        {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+        catch (IOException e)
+        {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+    }
+
+    private void loadSearchProvider(Properties properties)
+    {
+        //
+        // See if we're using Lucene, and if so, ensure that its
+        // index directory is up to date.
+        //
+        String useLucene = properties.getProperty(PROP_USE_LUCENE);
+
+        // FIXME: Obsolete, remove, or change logic to first load searchProvder?
+        // If the old jspwiki.useLucene property is set we use that instead of the searchProvider class.
+        if( useLucene != null )
+        {
+            log.info( PROP_USE_LUCENE+" is deprecated; please use "+PROP_SEARCHPROVIDER+"=<your search provider> instead." );
+            if( TextUtil.isPositive( useLucene ) )
+            {
+                m_searchProvider = new LuceneSearchProvider();
+            }
+            else
+            {
+                m_searchProvider = new BasicSearchProvider();
+            }
+            log.debug("useLucene was set, loading search provider " + m_searchProvider);
+            return;
+        }
+
+        String providerClassName = properties.getProperty( PROP_SEARCHPROVIDER,
+                                                           DEFAULT_SEARCHPROVIDER );
+
+        try
+        {
+            Class providerClass = ClassUtil.findClass( "com.ecyrd.jspwiki.search", providerClassName );
+            m_searchProvider = (SearchProvider)providerClass.newInstance();
+        }
+        catch( ClassNotFoundException e )
+        {
+            log.warn("Failed loading SearchProvider, will use BasicSearchProvider.", e);
+        }
+        catch( InstantiationException e )
+        {
+            log.warn("Failed loading SearchProvider, will use BasicSearchProvider.", e);
+        }
+        catch( IllegalAccessException e )
+        {
+            log.warn("Failed loading SearchProvider, will use BasicSearchProvider.", e);
+        }
+
+        if( null == m_searchProvider )
+        {
+            // FIXME: Make a static with the default search provider
+            m_searchProvider = new BasicSearchProvider();
+        }
+        log.debug("Loaded search provider " + m_searchProvider);
+    }
+
+    public SearchProvider getSearchEngine()
+    {
+        return m_searchProvider;
+    }
+
+    /**
+     *  Sends a search to the current search provider. The query is is whatever native format
+     *  the query engine wants to use.
+     *
+     * @param query The query.  Null is safe, and is interpreted as an empty query.
+     * @return A collection of WikiPages that matched.
+     */
+    public Collection findPages( String query )
+        throws ProviderException, IOException
+    {
+        if( query == null ) query = "";
+        Collection c = m_searchProvider.findPages( query );
+
+        return c;
+    }
+
+    /**
+     *  Removes the page from the search cache (if any).
+     *  @param page  The page to remove
+     */
+    public void pageRemoved(WikiPage page)
+    {
+        m_searchProvider.pageRemoved(page);
+    }
+
+    public void postSave( WikiContext wikiContext, String content )
+    {
+        //
+        //  Makes sure that we're indexing the latest version of this
+        //  page.
+        //
+        WikiPage p = m_engine.getPage( wikiContext.getPage().getName() );
+        reindexPage( p );
+    }
+
+    /**
+     *   Forces the reindex of the given page.
+     *
+     *   @param page
+     */
+    public void reindexPage(WikiPage page)
+    {
+        m_searchProvider.reindexPage(page);
+    }
+
+    public void actionPerformed(WikiEvent event)
+    {
+        if( (event instanceof WikiPageEvent) && (event.getType() == WikiPageEvent.PAGE_DELETE_REQUEST) )
+        {
+            String pageName = ((WikiPageEvent) event).getPageName();
+
+            WikiPage p = m_engine.getPage( pageName );
+            if( p != null )
+            {
+                pageRemoved( p );
+            }
+        }
+    }
+
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/search/SearchProvider.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/search/SearchProvider.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/search/SearchProvider.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/search/SearchProvider.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,61 @@
+/*
+JSPWiki - a JSP-based WikiWiki clone.
+
+Copyright (C) 2005 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+package com.ecyrd.jspwiki.search;
+
+import java.io.IOException;
+import java.util.Collection;
+
+import com.ecyrd.jspwiki.SearchResult;
+import com.ecyrd.jspwiki.WikiPage;
+import com.ecyrd.jspwiki.WikiProvider;
+import com.ecyrd.jspwiki.providers.ProviderException;
+
+/**
+ *  Interface for the search providers that handle searching the Wiki
+ *
+ *  @author Arent-Jan Banck
+ *  @since 2.2.21.
+ */
+public interface SearchProvider extends WikiProvider
+{
+    /**
+     * Delete a page from the search index
+     * @param page Page to remove from search index
+     */
+    public void pageRemoved(WikiPage page);
+
+    /**
+     *  Adds a WikiPage for indexing queue.  This is called a queue, since
+     *  this method is expected to return pretty quickly, and indexing to
+     *  be done in a separate thread.
+     *
+     *  @param page The WikiPage to be indexed.
+     */
+    public void reindexPage(WikiPage page);
+
+    /**
+     * Search for pages matching a search query
+     * @param query query to search for
+     * @return collection of pages that match query
+     * @throws ProviderException if the search provider failed.
+     * @throws IOException if for some reason the query could not be executed.
+     */
+    public Collection<SearchResult> findPages(String query) throws ProviderException, IOException;
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/tags/AdminBeanIteratorInfo.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/tags/AdminBeanIteratorInfo.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/tags/AdminBeanIteratorInfo.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/tags/AdminBeanIteratorInfo.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,46 @@
+/*
+    JSPWiki - a JSP-based WikiWiki clone.
+
+    Copyright (C) 2001-2007 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU Lesser General Public License as published by
+    the Free Software Foundation; either version 2.1 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+package com.ecyrd.jspwiki.tags;
+
+import javax.servlet.jsp.tagext.TagData;
+import javax.servlet.jsp.tagext.TagExtraInfo;
+import javax.servlet.jsp.tagext.VariableInfo;
+
+/**
+ *  Just provides iteration support for AdminBeanIteratorTag
+ *
+ *  @author jalkanen
+ *  @since 2.6.
+ */
+public class AdminBeanIteratorInfo extends TagExtraInfo
+{
+    public VariableInfo[] getVariableInfo(TagData data)
+    {
+        VariableInfo[] var = { new VariableInfo( data.getAttributeString("id"),
+                                                 "com.ecyrd.jspwiki.ui.admin.AdminBean",
+                                                 true,
+                                                 VariableInfo.NESTED )
+        };
+
+        return var;
+
+    }
+}
+

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/tags/AdminBeanIteratorTag.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/tags/AdminBeanIteratorTag.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/tags/AdminBeanIteratorTag.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/tags/AdminBeanIteratorTag.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,60 @@
+/*
+    JSPWiki - a JSP-based WikiWiki clone.
+
+    Copyright (C) 2001-2007 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU Lesser General Public License as published by
+    the Free Software Foundation; either version 2.1 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+package com.ecyrd.jspwiki.tags;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+
+import com.ecyrd.jspwiki.ui.admin.AdminBean;
+import com.ecyrd.jspwiki.ui.admin.AdminBeanManager;
+
+public class AdminBeanIteratorTag extends IteratorTag
+{
+    private static final long serialVersionUID = 1L;
+
+    private int m_type;
+
+    public void setType(String type)
+    {
+        m_type = AdminBeanManager.getTypeFromString( type );
+    }
+
+    public void resetIterator()
+    {
+        AdminBeanManager mgr = m_wikiContext.getEngine().getAdminBeanManager();
+
+        Collection beans = mgr.getAllBeans();
+        
+        ArrayList<AdminBean> typedBeans = new ArrayList<AdminBean>();
+        
+        for( Iterator i = beans.iterator(); i.hasNext(); )
+        {
+            AdminBean ab = (AdminBean)i.next();
+
+            if( ab.getType() == m_type )
+            {
+                typedBeans.add( ab );
+            }
+        }
+
+        setList( typedBeans );
+    }
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/tags/AttachmentsIteratorInfo.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/tags/AttachmentsIteratorInfo.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/tags/AttachmentsIteratorInfo.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/tags/AttachmentsIteratorInfo.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,44 @@
+/* 
+    JSPWiki - a JSP-based WikiWiki clone.
+
+    Copyright (C) 2001-2002 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU Lesser General Public License as published by
+    the Free Software Foundation; either version 2.1 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+package com.ecyrd.jspwiki.tags;
+
+import javax.servlet.jsp.tagext.TagExtraInfo;
+import javax.servlet.jsp.tagext.TagData;
+import javax.servlet.jsp.tagext.VariableInfo;
+
+/**
+ *  Just provides the TEI data for AttachmentsIteratorTag.
+ *
+ *  @since 2.0
+ */
+public class AttachmentsIteratorInfo extends TagExtraInfo
+{
+    public VariableInfo[] getVariableInfo(TagData data)
+    {
+        VariableInfo[] var = { new VariableInfo( data.getAttributeString("id"),
+                                                 "com.ecyrd.jspwiki.attachment.Attachment",
+                                                 true,
+                                                 VariableInfo.NESTED )
+        };
+
+        return var;
+
+    }
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/tags/AttachmentsIteratorTag.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/tags/AttachmentsIteratorTag.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/tags/AttachmentsIteratorTag.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/tags/AttachmentsIteratorTag.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,149 @@
+/* 
+    JSPWiki - a JSP-based WikiWiki clone.
+
+    Copyright (C) 2001-2002 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU Lesser General Public License as published by
+    the Free Software Foundation; either version 2.1 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+package com.ecyrd.jspwiki.tags;
+
+import java.io.IOException;
+import java.util.Collection;
+import javax.servlet.jsp.JspWriter;
+
+import org.apache.log4j.Logger;
+
+import com.ecyrd.jspwiki.WikiEngine;
+import com.ecyrd.jspwiki.WikiContext;
+import com.ecyrd.jspwiki.WikiPage;
+import com.ecyrd.jspwiki.providers.ProviderException;
+import com.ecyrd.jspwiki.action.WikiActionBeanFactory;
+import com.ecyrd.jspwiki.attachment.AttachmentManager;
+import com.ecyrd.jspwiki.attachment.Attachment;
+
+/**
+ *  Iterates through the list of attachments one has.
+ *
+ *  <P><B>Attributes</B></P>
+ *  <UL>
+ *    <LI>page - Page name to refer to.  Default is the current page.
+ *  </UL>
+ *
+ *  @author Janne Jalkanen
+ *  @since 2.0
+ */
+
+// FIXME: Too much in common with IteratorTag - REFACTOR
+public class AttachmentsIteratorTag
+    extends IteratorTag
+{
+    private static final long serialVersionUID = 0L;
+    
+    static    Logger    log = Logger.getLogger( AttachmentsIteratorTag.class );
+
+    public final int doStartTag()
+    {
+        m_wikiContext = (WikiContext)WikiActionBeanFactory.findActionBean( pageContext );
+
+        WikiEngine        engine = m_wikiContext.getEngine();
+        AttachmentManager mgr    = engine.getAttachmentManager();
+        WikiPage          page;
+
+        page = m_wikiContext.getPage();
+
+        if( !mgr.attachmentsEnabled() )
+        {
+            return SKIP_BODY;
+        }
+
+        try
+        {
+            if( page != null && engine.pageExists(page) )
+            {
+                Collection atts = mgr.listAttachments( page );
+
+                if( atts == null )
+                {
+                    log.debug("No attachments to display.");
+                    // There are no attachments included
+                    return SKIP_BODY;
+                }
+
+                m_iterator = atts.iterator();
+
+                if( m_iterator.hasNext() )
+                {
+                    Attachment  att = (Attachment) m_iterator.next();
+
+                    WikiContext context = (WikiContext)m_wikiContext.clone();
+                    context.setPage( att );
+                    WikiActionBeanFactory.saveActionBean( pageContext, context );
+
+                    pageContext.setAttribute( getId(), att );
+                }
+                else
+                {
+                    return SKIP_BODY;
+                }
+            }
+            else
+            {
+                return SKIP_BODY;
+            }
+
+            return EVAL_BODY_BUFFERED;
+        }
+        catch( ProviderException e )
+        {
+            log.fatal("Provider failed while trying to iterator through history",e);
+            // FIXME: THrow something.
+        }
+
+        return SKIP_BODY;
+    }
+
+    public final int doAfterBody()
+    {
+        if( bodyContent != null )
+        {
+            try
+            {
+                JspWriter out = getPreviousOut();
+                out.print(bodyContent.getString());
+                bodyContent.clearBody();
+            }
+            catch( IOException e )
+            {
+                log.error("Unable to get inner tag text", e);
+                // FIXME: throw something?
+            }
+        }
+
+        if( m_iterator != null && m_iterator.hasNext() )
+        {
+            Attachment att = (Attachment) m_iterator.next();
+
+            WikiContext context = (WikiContext)m_wikiContext.clone();
+            context.setPage( att );
+            WikiActionBeanFactory.saveActionBean( pageContext, context );
+
+            pageContext.setAttribute( getId(), att );
+
+            return EVAL_BODY_BUFFERED;
+        }
+
+        return SKIP_BODY;
+    }
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/tags/AuthorTag.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/tags/AuthorTag.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/tags/AuthorTag.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/tags/AuthorTag.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,84 @@
+/* 
+    JSPWiki - a JSP-based WikiWiki clone.
+
+    Copyright (C) 2001-2002 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU Lesser General Public License as published by
+    the Free Software Foundation; either version 2.1 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+package com.ecyrd.jspwiki.tags;
+
+import java.io.IOException;
+
+import com.ecyrd.jspwiki.WikiContext;
+import com.ecyrd.jspwiki.WikiEngine;
+import com.ecyrd.jspwiki.WikiPage;
+import com.ecyrd.jspwiki.TextUtil;
+import com.ecyrd.jspwiki.parser.MarkupParser;
+import com.ecyrd.jspwiki.parser.WikiDocument;
+import com.ecyrd.jspwiki.render.RenderingManager;
+
+/**
+ *  Writes the author name of the current page, including a link to that page,
+ *  if that page exists.
+ *
+ *  @author Janne Jalkanen
+ *  @since 2.0
+ */
+public class AuthorTag
+    extends WikiTagBase
+{
+    private static final long serialVersionUID = 0L;
+    
+    public final int doWikiStartTag()
+        throws IOException
+    {
+        if ( !( m_actionBean instanceof WikiContext ) )
+        {
+            return SKIP_BODY;
+        }
+        
+        WikiContext context = (WikiContext)m_actionBean;
+        WikiEngine engine = context.getEngine();
+        WikiPage   page   = context.getPage();
+
+        String author = page.getAuthor();
+
+        if( author != null && author.length() > 0 )
+        {
+            author = TextUtil.replaceEntities(author);
+            if( engine.pageExists(author) )
+            {
+                // FIXME: It's very boring to have to do this.
+                //        Slow, too.
+
+                RenderingManager mgr = engine.getRenderingManager();
+                
+                MarkupParser p = mgr.getParser( context, "["+author+"|"+author+"]" );
+
+                WikiDocument d = p.parse();
+                
+                author = mgr.getHTML( context, d );
+            }
+
+            pageContext.getOut().print( author );
+        }
+        else
+        {
+            pageContext.getOut().print( "unknown" );
+        }
+
+        return SKIP_BODY;
+    }
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/tags/BaseURLTag.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/tags/BaseURLTag.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/tags/BaseURLTag.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/tags/BaseURLTag.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,41 @@
+/* 
+    JSPWiki - a JSP-based WikiWiki clone.
+
+    Copyright (C) 2001-2005 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU Lesser General Public License as published by
+    the Free Software Foundation; either version 2.1 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+package com.ecyrd.jspwiki.tags;
+
+import java.io.IOException;
+
+/**
+ *  Writes the jspwiki.baseURL property.
+ *
+ *  @author Janne Jalkanen
+ *  @since 2.2
+ */
+public class BaseURLTag
+    extends WikiTagBase
+{
+    private static final long serialVersionUID = 0L;
+    
+    public final int doWikiStartTag()
+        throws IOException
+    {
+        pageContext.getOut().print( m_actionBean.getEngine().getBaseURL() );
+        return SKIP_BODY;
+    }
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/tags/BreadcrumbsTag.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/tags/BreadcrumbsTag.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/tags/BreadcrumbsTag.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/tags/BreadcrumbsTag.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,182 @@
+/*
+    JSPWiki - a JSP-based WikiWiki clone.
+
+    Copyright (C) 2001-2003 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU Lesser General Public License as published by
+    the Free Software Foundation; either version 2.1 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+package com.ecyrd.jspwiki.tags;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.LinkedList;
+
+import javax.servlet.http.HttpSession;
+import javax.servlet.jsp.JspWriter;
+
+import org.apache.log4j.Logger;
+
+import com.ecyrd.jspwiki.WikiContext;
+import com.ecyrd.jspwiki.action.ViewActionBean;
+
+/**
+ * Implement a "breadcrumb" (most recently visited) trail.  This tag can be added to any view jsp page.
+ * Separate breadcrumb trails are not tracked across multiple browser windows.<br>
+ * The optional attributes are:
+ * <p>
+ * <b>maxpages</b>, the number of pages to store, 10 by default<br>
+ * <b>separator</b>, the separator string to use between pages, " | " by default<br>
+ * </p>
+ *
+ * <p>
+ * This class is implemented by storing a breadcrumb trail, which is a
+ * fixed size queue, into a session variable "breadCrumbTrail".
+ * This queue is displayed as a series of links separated by a separator
+ * character.
+ * </p>
+ * @author Ken Liu ken@kenliu.net
+ */
+public class BreadcrumbsTag extends WikiTagBase
+{
+    private static final long serialVersionUID = 0L;
+
+    private static final Logger log = Logger.getLogger(BreadcrumbsTag.class);
+    private static final String BREADCRUMBTRAIL_KEY = "breadCrumbTrail";
+    private int m_maxQueueSize = 11;
+    private String m_separator = ", ";
+
+    public void initTag()
+    {
+        super.initTag();
+        m_maxQueueSize = 11;
+        m_separator = ", ";
+    }
+
+    public int getMaxpages()
+    {
+        return m_maxQueueSize;
+    }
+
+    public void setMaxpages(int maxpages)
+    {
+        m_maxQueueSize = maxpages + 1;
+    }
+
+    public String getSeparator()
+    {
+        return m_separator;
+    }
+
+    public void setSeparator(String separator)
+    {
+        m_separator = separator;
+    }
+
+    public int doWikiStartTag() throws IOException
+    {
+        HttpSession session = pageContext.getSession();
+        FixedQueue  trail   = (FixedQueue) session.getAttribute(BREADCRUMBTRAIL_KEY);
+
+        if( trail == null )
+        {
+            trail = new FixedQueue(m_maxQueueSize);
+        }
+
+        if( m_actionBean instanceof ViewActionBean && m_page != null )
+        {
+            String pageName = m_page.getName();
+            if( trail.isEmpty() )
+            {
+                trail.pushItem( pageName );
+            }
+            else
+            {
+                //
+                // Don't add the page to the queue if the page was just refreshed
+                //
+                if( !((String) trail.getLast()).equals( pageName ) )
+                {
+                    trail.pushItem( pageName );
+                    log.debug( "added page: " + pageName );
+                }
+                log.debug("didn't add page because of refresh");
+            }
+        }
+
+        session.setAttribute(BREADCRUMBTRAIL_KEY, trail);
+
+        //
+        //  Print out the breadcrumb trail
+        //
+
+        // FIXME: this code would be much simpler if we could just output the [pagename] and then use the
+        // wiki engine to output the appropriate wikilink
+
+        JspWriter out     = pageContext.getOut();
+        int queueSize     = trail.size();
+        String linkclass  = "wikipage";
+        String curPage    = null;
+
+        for( int i = 0; i < queueSize - 1; i++ )
+        {
+            curPage = (String) trail.get(i);
+
+            //FIXME: I can't figure out how to detect the appropriate jsp page to put here, so I hard coded Wiki.jsp
+            //This breaks when you view an attachment metadata page
+            if ( m_actionBean instanceof WikiContext )
+            {
+                WikiContext context = (WikiContext)m_actionBean;
+                out.print("<a class=\"" + linkclass + "\" href=\"" + 
+                          context.getViewURL(curPage)+ "\">" + curPage + "</a>");
+            }
+            
+            if( i < queueSize - 2 ) 
+            {
+                out.print(m_separator);
+            }
+        }
+
+        return SKIP_BODY;
+    }
+
+    /**
+     * Extends the LinkedList class to provide a fixed-size queue implementation
+     */
+    public static class FixedQueue
+        extends LinkedList<Object>
+        implements Serializable
+    {
+        private int m_size;
+        private static final long serialVersionUID = 0L;
+
+        FixedQueue(int size)
+        {
+            m_size = size;
+        }
+
+        Object pushItem(Object o)
+        {
+            add(o);
+            if( size() > m_size )
+            {
+                return removeFirst();
+            }
+
+            return null;
+        }
+    }
+
+}
+

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/tags/CalendarTag.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/tags/CalendarTag.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/tags/CalendarTag.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/tags/CalendarTag.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,345 @@
+/* 
+    JSPWiki - a JSP-based WikiWiki clone.
+
+    Copyright (C) 2001-2002 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU Lesser General Public License as published by
+    the Free Software Foundation; either version 2.1 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+package com.ecyrd.jspwiki.tags;
+
+import java.io.IOException;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.text.SimpleDateFormat;
+import java.text.ParseException;
+
+import javax.servlet.jsp.JspWriter;
+import javax.servlet.http.HttpServletRequest;
+
+import com.ecyrd.jspwiki.WikiEngine;
+import com.ecyrd.jspwiki.WikiContext;
+import com.ecyrd.jspwiki.TextUtil;
+import com.ecyrd.jspwiki.action.ViewActionBean;
+import com.ecyrd.jspwiki.providers.ProviderException;
+
+
+/**
+ *  Provides a nice calendar.  Responds to the following HTTP parameters:
+ *  <ul>
+ *  <li>calendar.date - If this parameter exists, then the calendar
+ *  date is taken from the month and year.  The date must be in ddMMyy
+ *  format.
+ *  <li>weblog.startDate - If calendar.date parameter does not exist,
+ *  we then check this date.
+ *  </ul>
+ *
+ *  If neither calendar.date nor weblog.startDate parameters exist,
+ *  then the calendar will default to the current month.
+ *
+ *
+ *  @author Janne Jalkanen
+ *  @since 2.0
+ */
+
+// FIXME: This class is extraordinarily lacking.
+
+public class CalendarTag
+    extends WikiTagBase
+{
+    private static final long serialVersionUID = 0L;
+    
+    private SimpleDateFormat m_pageFormat = null;
+    private SimpleDateFormat m_urlFormat = null;
+    private SimpleDateFormat m_monthUrlFormat = null;
+    private SimpleDateFormat m_dateFormat = new SimpleDateFormat( "ddMMyy" );
+
+    public void initTag()
+    {
+        super.initTag();
+        m_pageFormat = m_urlFormat = m_monthUrlFormat = null;
+        m_dateFormat = new SimpleDateFormat( "ddMMyy" );
+    }
+
+    /*
+    public void setYear( String year )
+    {
+        m_year = year;
+    }
+
+    public void setMonth( String month )
+    {
+        m_month = month;
+    }
+    */
+
+    public void setPageformat( String format )
+    {
+        m_pageFormat = new SimpleDateFormat(format);
+    }
+
+    public void setUrlformat( String format )
+    {
+        m_urlFormat = new SimpleDateFormat(format);
+    }
+
+    public void setMonthurlformat( String format )
+    {
+        m_monthUrlFormat = new SimpleDateFormat( format );
+    }
+
+    private String format( String txt )
+    {
+        if( m_page != null )
+        {
+            return TextUtil.replaceString( txt, "%p", m_page.getName() );
+        }
+
+        return txt;
+    }
+
+    /**
+     *  Returns a link to the given day.
+     */
+    private String getDayLink( Calendar day )
+    {
+        WikiEngine engine = m_actionBean.getEngine();
+        String result = "";
+
+        if( m_pageFormat != null )
+        {
+            String pagename = m_pageFormat.format( day.getTime() );
+            
+            if( engine.pageExists( pagename ) )
+            {
+                if( m_urlFormat != null )
+                {
+                    String url = m_urlFormat.format( day.getTime() );
+
+                    result = "<td class=\"link\"><a href=\""+url+"\">"+day.get( Calendar.DATE )+"</a></td>";
+                }
+                else
+                {
+                    WikiContext context = (WikiContext)m_actionBean;
+                    result = "<td class=\"link\"><a href=\""+context.getViewURL( pagename )+"\">"+
+                             day.get( Calendar.DATE )+"</a></td>";
+                }
+            }
+            else
+            {
+                result = "<td class=\"days\">"+day.get(Calendar.DATE)+"</td>";
+            }
+        }
+        else if( m_urlFormat != null )
+        {
+            String url = m_urlFormat.format( day.getTime() );
+
+            result = "<td><a href=\""+url+"\">"+day.get( Calendar.DATE )+"</a></td>";
+        }
+        else
+        {
+            result = "<td class=\"days\">"+day.get(Calendar.DATE)+"</td>";
+        }
+
+        return format(result);
+    }
+
+    private String getMonthLink( Calendar day )
+    {
+        SimpleDateFormat monthfmt = new SimpleDateFormat( "MMMM yyyy" );
+        String result;
+
+        if( m_monthUrlFormat == null )
+        {
+            result = monthfmt.format( day.getTime() );
+        }
+        else
+        {
+            Calendar cal = (Calendar)day.clone();
+            int firstDay = cal.getActualMinimum( Calendar.DATE );
+            int lastDay  = cal.getActualMaximum( Calendar.DATE );
+
+            cal.set( Calendar.DATE, lastDay );
+            String url = m_monthUrlFormat.format( cal.getTime() );
+
+            url = TextUtil.replaceString( url, "%d", Integer.toString( lastDay-firstDay+1 ) );
+
+            result = "<a href=\""+url+"\">"+monthfmt.format(cal.getTime())+"</a>";
+        }
+
+        return format(result);
+        
+    }
+    private String getMonthNaviLink( Calendar day, String txt, String queryString )
+    {
+        String result = "";
+        queryString = TextUtil.replaceEntities( queryString );
+        Calendar nextMonth = Calendar.getInstance();
+        nextMonth.set( Calendar.DATE, 1 );  
+        nextMonth.add( Calendar.DATE, -1);
+        nextMonth.add( Calendar.MONTH, 1 ); // Now move to 1st day of next month
+
+        if ( day.before(nextMonth) && m_page != null )
+        {
+            WikiContext context = (WikiContext)m_actionBean;
+            String pageName = m_page.getName();
+
+            String calendarDate = m_dateFormat.format(day.getTime());
+            Map<String,String> urlParams = new HashMap<String,String>();
+            urlParams.put("calendar.date", calendarDate );
+            String url = context.getContext().getURL( ViewActionBean.class, pageName, urlParams );
+
+            if ( (queryString != null) && (queryString.length() > 0) )
+            {
+                //
+                // Ensure that the 'calendar.date=ddMMyy' has been removed 
+                // from the queryString
+                //
+
+                // FIXME: Might be useful to have an entire library of 
+                //        routines for this.  Will fail if it's not calendar.date 
+                //        but something else.
+
+                int pos1 = queryString.indexOf("calendar.date=");
+                if (pos1 >= 0)
+                {
+                    String tmp = queryString.substring(0,pos1);
+                    // FIXME: Will this fail when we use & instead of &amp?
+                    // FIXME: should use some parsing routine
+                    int pos2 = queryString.indexOf("&",pos1) + 1;
+                    if ( (pos2 > 0) && (pos2 < queryString.length()) )
+                    {
+                        tmp = tmp + queryString.substring(pos2);
+                    }
+                    queryString = tmp;
+                }
+
+                if( queryString != null && queryString.length() > 0 )
+                {
+                    url = url + "&amp;"+queryString;
+                }
+            }
+            result = "<td><a href=\""+url+"\">"+txt+"</a></td>";
+        }
+        else
+        {
+            result="<td> </td>";
+        }    
+
+        return format(result);
+    }
+
+    public final int doWikiStartTag()
+        throws IOException,
+               ProviderException
+    {
+        WikiEngine       engine   = m_actionBean.getEngine();
+        JspWriter        out      = pageContext.getOut();
+        Calendar         cal      = Calendar.getInstance();
+        Calendar         prevCal  = Calendar.getInstance();
+        Calendar         nextCal  = Calendar.getInstance();
+
+        //
+        //  Check if there is a parameter in the request to set the date.
+        //
+        String calendarDate = pageContext.getRequest().getParameter( "calendar.date" );
+        if( calendarDate == null )
+        {
+            calendarDate = pageContext.getRequest().getParameter( "weblog.startDate" );
+        }
+        
+        if( calendarDate != null )
+        {
+            try
+            {
+                Date d = m_dateFormat.parse( calendarDate );
+                cal.setTime( d );
+                prevCal.setTime( d );
+                nextCal.setTime( d );
+            }
+            catch( ParseException e )
+            {
+                log.warn( "date format wrong: "+calendarDate );
+            }
+        }
+
+        cal.set( Calendar.DATE, 1 );     // First, set to first day of month
+        prevCal.set( Calendar.DATE, 1 );
+        nextCal.set( Calendar.DATE, 1 );
+
+        prevCal.add(Calendar.MONTH, -1); // Now move to first day of previous month
+        nextCal.add(Calendar.MONTH, 1);  // Now move to first day of next month
+
+        out.write( "<table class=\"calendar\">\n" );
+
+        HttpServletRequest httpServletRequest = m_actionBean.getContext().getRequest();
+        String queryString = engine.safeGetQueryString( httpServletRequest );
+        out.write( "<tr>"+
+                   getMonthNaviLink(prevCal,"&lt;&lt;", queryString)+
+                   "<td colspan=5 class=\"month\">"+
+                   getMonthLink( cal )+
+                   "</td>"+
+                   getMonthNaviLink(nextCal,"&gt;&gt;", queryString)+ 
+                   "</tr>\n"
+                 );
+
+        int month = cal.get( Calendar.MONTH );
+        cal.set( Calendar.DAY_OF_WEEK, Calendar.MONDAY ); // Then, find the first day of the week.
+
+        out.write( "<tr><td class=\"weekdays\">Mon</td>"+
+                   "<td class=\"weekdays\">Tue</td>"+
+                   "<td class=\"weekdays\">Wed</td>"+
+                   "<td class=\"weekdays\">Thu</td>"+
+                   "<td class=\"weekdays\">Fri</td>"+
+                   "<td class=\"weekdays\">Sat</td>"+
+                   "<td class=\"weekdays\">Sun</td></tr>\n" );
+
+        boolean noMoreDates = false;
+        while( !noMoreDates )
+        {
+            out.write( "<tr>" );
+            
+            for( int i = 0; i < 7; i++ )
+            {
+                int mth = cal.get( Calendar.MONTH );
+
+                if( mth != month )
+                {
+                    out.write("<td class=\"othermonth\">"+cal.get(Calendar.DATE)+"</td>");
+                }
+                else
+                {
+                    out.write( getDayLink(cal) );
+                }
+
+                cal.add( Calendar.DATE, 1 );
+            }
+
+            if( cal.get( Calendar.MONTH ) != month )
+            {
+                noMoreDates = true;
+            }
+
+            out.write( "</tr>\n" );
+        }
+
+        out.write( "</table>\n" );
+
+        return EVAL_BODY_INCLUDE;
+    }
+
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/tags/CheckLockInfo.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/tags/CheckLockInfo.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/tags/CheckLockInfo.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/tags/CheckLockInfo.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,39 @@
+/*
+    JSPWiki - a JSP-based WikiWiki clone.
+
+    Copyright (C) 2001-2007 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU Lesser General Public License as published by
+    the Free Software Foundation; either version 2.1 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+package com.ecyrd.jspwiki.tags;
+
+import javax.servlet.jsp.tagext.TagExtraInfo;
+import javax.servlet.jsp.tagext.TagData;
+import javax.servlet.jsp.tagext.VariableInfo;
+
+public class CheckLockInfo
+    extends TagExtraInfo
+{
+    public VariableInfo[] getVariableInfo(TagData data)
+    {
+        VariableInfo[] var = { new VariableInfo( data.getAttributeString("id"),
+                                                 "com.ecyrd.jspwiki.PageLock",
+                                                 true,
+                                                 VariableInfo.NESTED )
+        };
+
+        return var;
+    }
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/tags/CheckLockTag.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/tags/CheckLockTag.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/tags/CheckLockTag.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/tags/CheckLockTag.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,103 @@
+/* 
+    JSPWiki - a JSP-based WikiWiki clone.
+
+    Copyright (C) 2001-2002 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU Lesser General Public License as published by
+    the Free Software Foundation; either version 2.1 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+package com.ecyrd.jspwiki.tags;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpSession;
+
+import com.ecyrd.jspwiki.PageLock;
+import com.ecyrd.jspwiki.PageManager;
+import com.ecyrd.jspwiki.WikiEngine;
+import com.ecyrd.jspwiki.providers.ProviderException;
+
+/**
+ *
+ *  @author Janne Jalkanen
+ *  @since 2.0
+ */
+public class CheckLockTag
+    extends WikiTagBase
+{
+    private static final long serialVersionUID = 0L;
+    
+    public static final int LOCKED    = 0;
+    public static final int NOTLOCKED = 1;
+    public static final int OWNED     = 2;
+
+    private int m_mode;
+
+    public void initTag()
+    {
+        super.initTag();
+        m_mode = 0;
+    }
+
+    public void setMode( String arg )
+    {
+        if( "locked".equals(arg) )
+        {
+            m_mode = LOCKED;
+        }
+        else if("owned".equals(arg) )
+        {
+            m_mode = OWNED;
+        }
+        else
+        {
+            m_mode = NOTLOCKED;
+        }
+    }
+
+    public final int doWikiStartTag()
+        throws IOException,
+               ProviderException
+    {
+        WikiEngine engine = m_actionBean.getEngine();
+
+        if( m_page != null )
+        {
+            PageManager mgr = engine.getPageManager();
+
+            PageLock lock = mgr.getCurrentLock( m_page );
+
+            HttpSession session = pageContext.getSession();
+
+            PageLock userLock = (PageLock) session.getAttribute("lock-"+m_page.getName());
+
+            if( (lock != null && m_mode == LOCKED && lock != userLock ) ||
+                (lock != null && m_mode == OWNED && lock == userLock ) ||
+                (lock == null && m_mode == NOTLOCKED) )
+            {
+                String tid = getId();
+
+                if( tid != null && lock != null )
+                {
+                    pageContext.setAttribute( tid, lock );
+                }
+
+                return EVAL_BODY_INCLUDE;
+            }
+        }
+
+        return SKIP_BODY;
+    }
+
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/tags/CheckRequestContextTag.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/tags/CheckRequestContextTag.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/tags/CheckRequestContextTag.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/tags/CheckRequestContextTag.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,90 @@
+/* 
+    JSPWiki - a JSP-based WikiWiki clone.
+
+    Copyright (C) 2001-2002 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU Lesser General Public License as published by
+    the Free Software Foundation; either version 2.1 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+package com.ecyrd.jspwiki.tags;
+
+import java.io.IOException;
+
+import org.apache.commons.lang.StringUtils;
+
+import com.ecyrd.jspwiki.providers.ProviderException;
+
+/**
+ *  Includes body, if the request context matches.
+ *
+ *  @author Janne Jalkanen
+ *  @since 2.0
+ */
+public class CheckRequestContextTag
+    extends WikiTagBase
+{
+    private static final long serialVersionUID = 0L;
+    
+    private String m_context;
+    private String[] m_contextList = {};
+
+
+    public void initTag()
+    {
+        super.initTag();
+        m_context = null;
+        m_contextList = new String[0];
+    }
+    
+    public String getContext()
+    {
+        return m_context;
+    }
+
+    public void setContext( String arg )
+    {
+        m_context = arg;
+        
+        m_contextList = StringUtils.split(arg,'|');
+    }
+
+    public final int doWikiStartTag()
+        throws IOException,
+               ProviderException
+    {
+        for(int i = 0; i < m_contextList.length; i++ )
+        {
+            String ctx = m_actionBean.getRequestContext();
+            
+            String checkedCtx = m_contextList[i];
+
+            if( checkedCtx.length() > 0 )
+            {
+                if( checkedCtx.charAt(0) == '!' )
+                {
+                    if( !ctx.equalsIgnoreCase(checkedCtx.substring(1) ) )
+                    {
+                        return EVAL_BODY_INCLUDE;
+                    }
+                }
+                else if( ctx.equalsIgnoreCase(m_contextList[i]) )
+                {
+                    return EVAL_BODY_INCLUDE;
+                }
+            }
+        }
+
+        return SKIP_BODY;
+    }
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/tags/CheckVersionTag.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/tags/CheckVersionTag.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/tags/CheckVersionTag.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/tags/CheckVersionTag.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,121 @@
+/* 
+    JSPWiki - a JSP-based WikiWiki clone.
+
+    Copyright (C) 2001-2002 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU Lesser General Public License as published by
+    the Free Software Foundation; either version 2.1 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+package com.ecyrd.jspwiki.tags;
+
+import java.io.IOException;
+
+import com.ecyrd.jspwiki.WikiEngine;
+import com.ecyrd.jspwiki.WikiPage;
+import com.ecyrd.jspwiki.providers.ProviderException;
+
+/**
+ *  Does a version check on the page.  Mode is as follows:
+ *  <UL>
+ *   <LI>latest = Include page content, if the page is the latest version.
+ *   <LI>notlatest = Include page content, if the page is NOT the latest version.
+ *  </UL>
+ *  If the page does not exist, body content is never included.
+ *
+ *  @author Janne Jalkanen
+ *  @since 2.0
+ */
+public class CheckVersionTag
+    extends WikiTagBase
+{
+    private static final long serialVersionUID = 0L;
+    
+    public static final int LATEST    = 0;
+    public static final int NOTLATEST = 1;
+    public static final int FIRST     = 2;
+    public static final int NOTFIRST  = 3;
+
+    private int m_mode;
+
+    public void initTag()
+    {
+        super.initTag();
+        m_mode = 0;
+    }
+
+    public void setMode( String arg )
+    {
+        if( "latest".equals(arg) )
+        {
+            m_mode = LATEST;
+        }
+        else if( "notfirst".equals(arg) )
+        {
+            m_mode = NOTFIRST;
+        }
+        else if( "first".equals(arg) )
+        {
+            m_mode = FIRST;
+        }
+        else
+        {
+            m_mode = NOTLATEST;
+        }
+    }
+
+    public final int doWikiStartTag()
+        throws IOException,
+               ProviderException
+    {
+        WikiEngine engine = m_actionBean.getEngine();
+
+        if( m_page != null && engine.pageExists(m_page.getName()) )
+        {
+            int version = m_page.getVersion();
+            boolean include = false;
+
+            WikiPage latest = engine.getPage( m_page.getName() );
+
+            //log.debug("Doing version check: this="+page.getVersion()+
+            //          ", latest="+latest.getVersion());
+
+            switch( m_mode )
+            {
+              case LATEST:
+                include = (version < 0) || (latest.getVersion() == version);
+                break;
+
+              case NOTLATEST:
+                include = (version > 0) && (latest.getVersion() != version);
+                break;
+
+              case FIRST:
+                include = (version == 1 ) || (version < 0 && latest.getVersion() == 1);
+                break;
+
+              case NOTFIRST:
+                include = version > 1;
+                break;
+            }
+
+            if( include )
+            {
+                // log.debug("INCLD");
+                return EVAL_BODY_INCLUDE;
+            }
+        }
+
+        return SKIP_BODY;
+    }
+}