jspwiki-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ajaqu...@apache.org
Subject svn commit: r627255 [28/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...
Date Wed, 13 Feb 2008 05:54:24 GMT
Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/plugin/WeblogPlugin.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/plugin/WeblogPlugin.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/plugin/WeblogPlugin.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/plugin/WeblogPlugin.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,467 @@
+/*
+    JSPWiki - a JSP-based WikiWiki clone.
+
+    Copyright (C) 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.plugin;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.log4j.Logger;
+
+import com.ecyrd.jspwiki.*;
+import com.ecyrd.jspwiki.auth.AuthorizationManager;
+import com.ecyrd.jspwiki.auth.permissions.PagePermission;
+import com.ecyrd.jspwiki.action.CommentActionBean;
+import com.ecyrd.jspwiki.action.ViewActionBean;
+import com.ecyrd.jspwiki.parser.PluginContent;
+import com.ecyrd.jspwiki.providers.ProviderException;
+
+/**
+ *  <p>Builds a simple weblog.
+ *  The pageformat can use the following params:</p>
+ *  <p>%p - Page name</p>
+ *  <p>Parameters:</p>
+ *  <ul>
+ *    <li>page - which page is used to do the blog; default is the current page.</li>
+ *    <li>entryFormat - how to display the date on pages, using the J2SE SimpleDateFormat
+ *       syntax. Defaults to the current locale's DateFormat.LONG format
+ *       for the date, and current locale's DateFormat.SHORT for the time.
+ *       Thus, for the US locale this will print dates similar to
+ *       this: September 4, 2005 11:54 PM</li>
+ *    <li>days - how many days the weblog aggregator should show.  If set to
+ *      "all", shows all pages.</li>
+ *    <li>pageformat - What the entry pages should look like.</li>
+ *    <li>startDate - Date when to start.  Format is "ddMMyy."</li>
+ *    <li>maxEntries - How many entries to show at most.</li>
+ *  </ul>
+ *  <p>The "days" and "startDate" can also be sent in HTTP parameters,
+ *  and the names are "weblog.days" and "weblog.startDate", respectively.</p>
+ *  <p>The weblog plugin also adds an attribute to each page it is on:
+ *  "weblogplugin.isweblog" is set to "true".  This can be used to quickly
+ *  peruse pages which have weblogs.</p>
+ *  @since 1.9.21
+ */
+
+// FIXME: Add "entries" param as an alternative to "days".
+// FIXME: Entries arrive in wrong order.
+
+public class WeblogPlugin
+    implements WikiPlugin, ParserStagePlugin
+{
+    private static Logger     log = Logger.getLogger(WeblogPlugin.class);
+    private static final DateFormat DEFAULT_ENTRYFORMAT
+                                = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.SHORT);
+    private static final Pattern headingPattern;
+
+    /** How many days are considered by default.  Default value is {@value} */
+    public static final int     DEFAULT_DAYS = 7;
+    public static final String  DEFAULT_PAGEFORMAT = "%p_blogentry_";
+
+    public static final String  DEFAULT_DATEFORMAT = "ddMMyy";
+
+    public static final String  PARAM_STARTDATE    = "startDate";
+    public static final String  PARAM_ENTRYFORMAT  = "entryFormat";
+    public static final String  PARAM_DAYS         = "days";
+    public static final String  PARAM_ALLOWCOMMENTS = "allowComments";
+    public static final String  PARAM_MAXENTRIES   = "maxEntries";
+    public static final String  PARAM_PAGE         = "page";
+
+    public static final String  ATTR_ISWEBLOG      = "weblogplugin.isweblog";
+
+    static
+    {
+        // This is a pretty ugly, brute-force regex. But it will do for now...
+        headingPattern = Pattern.compile("(<h[1-4].*>)(.*)(</h[1-4]>)", Pattern.CASE_INSENSITIVE);
+    }
+
+    public static String makeEntryPage( String pageName,
+                                        String date,
+                                        String entryNum )
+    {
+        return TextUtil.replaceString(DEFAULT_PAGEFORMAT,"%p",pageName)+date+"_"+entryNum;
+    }
+
+    public static String makeEntryPage( String pageName )
+    {
+        return TextUtil.replaceString(DEFAULT_PAGEFORMAT,"%p",pageName);
+    }
+
+    public static String makeEntryPage( String pageName, String date )
+    {
+        return TextUtil.replaceString(DEFAULT_PAGEFORMAT,"%p",pageName)+date;
+    }
+
+    public String execute( WikiContext context, Map params )
+        throws PluginException
+    {
+        Calendar   startTime;
+        Calendar   stopTime;
+        int        numDays = DEFAULT_DAYS;
+        WikiEngine engine = context.getEngine();
+        AuthorizationManager mgr = engine.getAuthorizationManager();
+        
+        //
+        //  Parse parameters.
+        //
+        String  days;
+        DateFormat entryFormat;
+        String  startDay = null;
+        boolean hasComments = false;
+        int     maxEntries;
+        String  weblogName;
+
+        if( (weblogName = (String) params.get(PARAM_PAGE)) == null )
+        {
+            weblogName = context.getPage().getName();
+        }
+
+        if( (days = context.getHttpParameter( "weblog."+PARAM_DAYS )) == null )
+        {
+            days = (String) params.get( PARAM_DAYS );
+        }
+
+        if( ( params.get(PARAM_ENTRYFORMAT)) == null )
+        {
+            entryFormat = DEFAULT_ENTRYFORMAT;
+        }
+        else
+        {
+            entryFormat = new SimpleDateFormat( (String)params.get(PARAM_ENTRYFORMAT) );
+        }
+
+        if( days != null )
+        {
+            if( days.equalsIgnoreCase("all") )
+            {
+                numDays = Integer.MAX_VALUE;
+            }
+            else
+            {
+                numDays = TextUtil.parseIntParameter( days, DEFAULT_DAYS );
+            }
+        }
+
+
+        if( (startDay = (String)params.get(PARAM_STARTDATE)) == null )
+        {
+            startDay = context.getHttpParameter( "weblog."+PARAM_STARTDATE );
+        }
+
+        if( TextUtil.isPositive( (String)params.get(PARAM_ALLOWCOMMENTS) ) )
+        {
+            hasComments = true;
+        }
+
+        maxEntries = TextUtil.parseIntParameter( (String)params.get(PARAM_MAXENTRIES),
+                                                 Integer.MAX_VALUE );
+
+        //
+        //  Determine the date range which to include.
+        //
+
+        startTime = Calendar.getInstance();
+        stopTime  = Calendar.getInstance();
+
+        if( startDay != null )
+        {
+            SimpleDateFormat fmt = new SimpleDateFormat( DEFAULT_DATEFORMAT );
+            try
+            {
+                Date d = fmt.parse( startDay );
+                startTime.setTime( d );
+                stopTime.setTime( d );
+            }
+            catch( ParseException e )
+            {
+                return "Illegal time format: "+startDay;
+            }
+        }
+
+        //
+        //  Mark this to be a weblog
+        //
+
+        context.getPage().setAttribute(ATTR_ISWEBLOG, "true");
+
+        //
+        //  We make a wild guess here that nobody can do millisecond
+        //  accuracy here.
+        //
+        startTime.add( Calendar.DAY_OF_MONTH, -numDays );
+        startTime.set( Calendar.HOUR, 0 );
+        startTime.set( Calendar.MINUTE, 0 );
+        startTime.set( Calendar.SECOND, 0 );
+        stopTime.set( Calendar.HOUR, 23 );
+        stopTime.set( Calendar.MINUTE, 59 );
+        stopTime.set( Calendar.SECOND, 59 );
+
+        StringBuffer sb = new StringBuffer();
+
+        try
+        {
+            List<WikiPage> blogEntries = findBlogEntries( engine.getPageManager(),
+                                                weblogName,
+                                                startTime.getTime(),
+                                                stopTime.getTime() );
+
+            Collections.sort( blogEntries, new PageDateComparator() );
+
+            sb.append("<div class=\"weblog\">\n");
+            
+            for( Iterator i = blogEntries.iterator(); i.hasNext() && maxEntries-- > 0 ; )
+            {
+                WikiPage p = (WikiPage) i.next();
+
+                if( mgr.checkPermission( context.getWikiSession(), 
+                                         new PagePermission(p, PagePermission.VIEW_ACTION) ) )
+                {
+                    addEntryHTML(context, entryFormat, hasComments, sb, p);
+                }
+            }
+
+            sb.append("</div>\n");
+        }
+        catch( ProviderException e )
+        {
+            log.error( "Could not locate blog entries", e );
+            throw new PluginException( "Could not locate blog entries: "+e.getMessage() );
+        }
+
+        return sb.toString();
+    }
+
+    /**
+     *  Generates HTML for an entry.
+     *  
+     *  @param context
+     *  @param entryFormat
+     *  @param hasComments  True, if comments are enabled.
+     *  @param buffer       The buffer to which we add.
+     *  @param entry
+     *  @throws ProviderException
+     */
+    private void addEntryHTML(WikiContext context, DateFormat entryFormat, boolean hasComments, StringBuffer buffer, WikiPage entry) 
+        throws ProviderException
+    {
+        WikiEngine engine = context.getEngine();
+        buffer.append("<div class=\"weblogentry\">\n");
+
+        //
+        //  Heading
+        //
+        buffer.append("<div class=\"weblogentryheading\">\n");
+
+        Date entryDate = entry.getLastModified();
+        buffer.append( entryFormat.format(entryDate) );
+
+        buffer.append("</div>\n");
+
+        //
+        //  Append the text of the latest version.  Reset the
+        //  context to that page.
+        //
+
+        WikiContext entryCtx = (WikiContext) context.clone();
+        entryCtx.setPage( entry );
+
+        String html = engine.getHTML( entryCtx, engine.getPage(entry.getName()) );
+
+        // Extract the first h1/h2/h3 as title, and replace with null
+        buffer.append("<div class=\"weblogentrytitle\">\n");
+        Matcher matcher = headingPattern.matcher( html );
+        if ( matcher.find() )
+        {
+            String title = matcher.group(2);
+            html = matcher.replaceFirst("");
+            buffer.append( title );
+        }
+        else
+        {
+            buffer.append( entry.getName() );
+        }
+        buffer.append("</div>\n");
+
+        buffer.append("<div class=\"weblogentrybody\">\n");
+        buffer.append( html );
+        buffer.append("</div>\n");
+
+        //
+        //  Append footer
+        //
+        buffer.append("<div class=\"weblogentryfooter\">\n");
+            
+        String author = entry.getAuthor();
+
+        if( author != null )
+        {
+            if( engine.pageExists(author) )
+            {
+                author = "<a href=\""+entryCtx.getContext().getURL( ViewActionBean.class, author )+"\">"+engine.beautifyTitle(author)+"</a>";
+            }
+        }
+        else
+        {
+            author = "AnonymousCoward";
+        }
+
+        buffer.append("By "+author+"&nbsp;&nbsp;");
+        buffer.append( "<a href=\""+entryCtx.getContext().getURL( ViewActionBean.class, entry.getName())+"\">Permalink</a>" );
+        String commentPageName = TextUtil.replaceString( entry.getName(),
+                                                         "blogentry",
+                                                         "comments" );
+
+        if( hasComments )
+        {
+            int numComments = guessNumberOfComments( engine, commentPageName );
+
+            //
+            //  We add the number of comments to the URL so that
+            //  the user's browsers would realize that the page
+            //  has changed.
+            //
+            buffer.append( "&nbsp;&nbsp;" );
+            Map<String,String> urlParams = new HashMap<String,String>();
+            urlParams.put("nc",String.valueOf(numComments));
+            buffer.append( "<a target=\"_blank\" href=\""+
+                       entryCtx.getContext().getURL(CommentActionBean.class, commentPageName, urlParams) +
+                       "\">Comments? ("+
+                       numComments+
+                       ")</a>" );
+        }
+
+        buffer.append("</div>\n");
+
+        //
+        //  Done, close
+        //
+        buffer.append("</div>\n");
+    }
+
+    private int guessNumberOfComments( WikiEngine engine, String commentpage )
+        throws ProviderException
+    {
+        String pagedata = engine.getPureText( commentpage, WikiProvider.LATEST_VERSION );
+
+        if( pagedata == null || pagedata.trim().length() == 0 )
+        {
+            return 0;
+        }
+
+        return TextUtil.countSections( pagedata );
+    }
+
+    /**
+     *  Attempts to locate all pages that correspond to the
+     *  blog entry pattern.  Will only consider the days on the dates; not the hours and minutes.
+     *
+     *  @param mgr A PageManager which is used to get the pages
+     *  @param baseName The basename (e.g. "Main" if you want "Main_blogentry_xxxx")
+     *  @param start The date which is the first to be considered
+     *  @param end   The end date which is the last to be considered
+     *  @return a list of pages with their FIRST revisions.
+     *  @throws ProviderException If something goes wrong
+     */
+    public List<WikiPage> findBlogEntries( PageManager mgr,
+                                 String baseName, Date start, Date end )
+        throws ProviderException
+    {
+        Collection<WikiPage> everyone = mgr.getAllPages();
+        List<WikiPage>  result = new ArrayList<WikiPage>();
+
+        baseName = makeEntryPage( baseName );
+        SimpleDateFormat fmt = new SimpleDateFormat(DEFAULT_DATEFORMAT);
+
+        for( WikiPage p : everyone )
+        {
+            String pageName = p.getName();
+
+            if( pageName.startsWith( baseName ) )
+            {
+                //
+                //  Check the creation date from the page name.
+                //  We do this because RCSFileProvider is very slow at getting a
+                //  specific page version.
+                //
+                try
+                {
+                    //log.debug("Checking: "+pageName);
+                    int firstScore = pageName.indexOf('_',baseName.length()-1 );
+                    if( firstScore != -1 && firstScore+1 < pageName.length() )
+                    {
+                        int secondScore = pageName.indexOf('_', firstScore+1);
+
+                        if( secondScore != -1 )
+                        {
+                            String creationDate = pageName.substring( firstScore+1, secondScore );
+
+                            //log.debug("   Creation date: "+creationDate);
+
+                            Date pageDay = fmt.parse( creationDate );
+
+                            //
+                            //  Add the first version of the page into the list.  This way
+                            //  the page modified date becomes the page creation date.
+                            //
+                            if( pageDay != null && pageDay.after(start) && pageDay.before(end) )
+                            {
+                                WikiPage firstVersion = mgr.getPageInfo( pageName, 1 );
+                                result.add( firstVersion );
+                            }
+                        }
+                    }
+                }
+                catch( Exception e )
+                {
+                    log.debug("Page name :"+pageName+" was suspected as a blog entry but it isn't because of parsing errors",e);
+                }
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     *  Reverse comparison.
+     */
+    private static class PageDateComparator implements Comparator<WikiPage>
+    {
+        public int compare( WikiPage o1, WikiPage o2 )
+        {
+            if( o1 == null || o2 == null )
+            {
+                return 0;
+            }
+
+            return o2.getLastModified().compareTo( o1.getLastModified() );
+        }
+    }
+
+    /** 
+     *  Mark us as being a real weblog. 
+     *  {@inheritDoc}
+     */
+    public void executeParser(PluginContent element, WikiContext context, Map params)
+    {
+        context.getPage().setAttribute( ATTR_ISWEBLOG, "true" );
+    }
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/plugin/WikiPlugin.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/plugin/WikiPlugin.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/plugin/WikiPlugin.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/plugin/WikiPlugin.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,61 @@
+/* 
+    JSPWiki - a JSP-based WikiWiki clone.
+
+    Copyright (C) 2001 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.plugin;
+
+import java.util.Map;
+
+import com.ecyrd.jspwiki.WikiContext;
+
+/**
+ *  Defines an interface for plugins.  Any instance of a wiki plugin
+ *  should implement this interface.
+ *
+ *  @author Janne Jalkanen
+ */
+public interface WikiPlugin
+{
+    static final String CORE_PLUGINS_RESOURCEBUNDLE = "com.ecyrd.jspwiki.plugin.PluginResources";
+
+    /**
+     *  This is the main entry point for any plugin.  The parameters are parsed,
+     *  and a special parameter called "_body" signifies the name of the plugin
+     *  body, i.e. the part of the plugin that is not a parameter of
+     *  the form "key=value".  This has been separated using an empty
+     *  line.
+     *  <P>
+     *  Note that it is preferred that the plugin returns
+     *  XHTML-compliant HTML (i.e. close all tags, use &lt;br /&gt;
+     *  instead of &lt;br&gt;, etc.
+     *
+     *  @param context The current WikiContext.
+     *  @param params  A Map which contains key-value pairs.  Any
+     *                 parameter that the user has specified on the
+     *                 wiki page will contain String-String
+     *  parameters, but it is possible that at some future date,
+     *  JSPWiki will give you other things that are not Strings.
+     *
+     *  @return HTML, ready to be included into the rendered page.
+     *
+     *  @throws PluginException In case anything goes wrong.
+     */
+
+    public String execute( WikiContext context, Map params )
+        throws PluginException;
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/plugin/denounce.properties
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/plugin/denounce.properties?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/plugin/denounce.properties (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/plugin/denounce.properties Tue Feb 12 21:53:55 2008
@@ -0,0 +1,30 @@
+#
+#  This file contains different patterns for the Denounce plugin.
+#
+#  Please send me new patterns as you figure them out.
+#
+
+denounce.denouncetext = &lt;Link removed because I do not wish to endorse this link to search engines&gt;
+
+#
+#  Which user agents should be denounced.
+#
+denounce.agentpattern.googlebot = *Googlebot*
+denounce.agentpattern.inktomi   = *Slurp/*
+denounce.agentpattern.fastsearch = *FAST-WebCrawler/*
+denounce.agentpattern.voila     = *VoilaBot*
+denounce.agentpattern.ms        = *MicrosoftPrototypeCrawler*
+denounce.agentpattern.grub      = *grub-client*
+#
+#  Which referers should be denounced.
+#
+#denounce.refererpattern = http://localhost/*
+
+#
+#  Which hostpatterns should be denounced.  This uses the result
+#  of ServletRequest.getRemoteHost(), which may return an IP address as
+#  well, if the name cannot be determined.
+#
+denounce.hostpattern.googlebot = *.googlebot.com
+denounce.hostpattern.inktomi   = *.inktomisearch.com
+denounce.hostpattern.alexa     = crawl*.alexa.com

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/plugin/package.html
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/plugin/package.html?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/plugin/package.html (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/plugin/package.html Tue Feb 12 21:53:55 2008
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html> <head>
+<title>com.ecyrd.jspwiki.plugin</title>
+</head>
+
+<body>
+
+Provides plugins to the JSPWiki.
+
+<h2>Package Specification</h2>
+
+This package contains all plugin-related classes, interfaces and helpers.
+
+<h2>Related Documentation</h2>
+
+<hr>
+<!-- hhmts start -->Last modified: Sun Nov 14 14:12:36 EET 2004 <!-- hhmts end -->
+</body> </html>

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/preferences/Preferences.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/preferences/Preferences.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/preferences/Preferences.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/preferences/Preferences.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,146 @@
+package com.ecyrd.jspwiki.preferences;
+
+import java.text.ParseException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Properties;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+import javax.servlet.jsp.PageContext;
+
+import org.json.JSONObject;
+
+import com.ecyrd.jspwiki.PropertyReader;
+import com.ecyrd.jspwiki.TextUtil;
+import com.ecyrd.jspwiki.WikiContext;
+import com.ecyrd.jspwiki.util.HttpUtil;
+
+/**
+ *  Represents an object which is used to store user preferences.
+ *  
+ *  @author jalkanen
+ */
+public class Preferences
+    extends HashMap
+{
+    private static final long serialVersionUID = 1L;
+    
+    /**
+     *  The name under which a Preferences object is stored in the HttpSession.
+     *  Its value is {@value}.
+     */
+    public static final String SESSIONPREFS = "prefs";
+     
+    /**
+     *  This is an utility method which is called to make sure that the
+     *  JSP pages do have proper access to any user preferences.  It should be
+     *  called from the commonheader.jsp.
+     *  <p>
+     *  This method reads user cookie preferences and mixes them up with any
+     *  default preferences (and in the future, any user-specific preferences)
+     *  and puts them all in the session, so that they do not have to be rewritten
+     *  again.
+     *  <p>
+     *  This method will remember if the user has already changed his prefs.
+     *  
+     *  @param pageContext The JSP PageContext.
+     */
+    public static void setupPreferences( PageContext pageContext )
+    {
+        HttpSession session = pageContext.getSession();
+
+        if( session.getAttribute( SESSIONPREFS ) == null )
+        {
+            reloadPreferences( pageContext );
+        }
+    }
+    
+    public static void reloadPreferences( PageContext pageContext )
+    {
+        Preferences prefs = new Preferences();
+        Properties props = PropertyReader.loadWebAppProps( pageContext.getServletContext() );
+        
+        prefs.put("SkinName", TextUtil.getStringProperty( props, "jspwiki.defaultprefs.template.skinname", "PlainVanilla" ) );
+        prefs.put("DateFormat", TextUtil.getStringProperty( props, "jspwiki.defaultprefs.template.dateformat", "dd-MMM-yyyy HH:mm" ) );
+        prefs.put("TimeZone", TextUtil.getStringProperty( props, "jspwiki.defaultprefs.template.timezone", 
+                                                          java.util.TimeZone.getDefault().getID() ) );
+        prefs.put("orientation", TextUtil.getStringProperty( props, "jspwiki.defaultprefs.template.orientation", "fav-left" ) );
+        
+        // FIXME: "editor" property does not get registered, may be related with http://bugs.jspwiki.org/show_bug.cgi?id=117
+        // disabling it until knowing why it's happening
+        // FIXME: editomanager reads jspwiki.editor -- which of both properties should continue
+        prefs.put("editor", TextUtil.getStringProperty( props, "jspwiki.defaultprefs.template.editor", "plain" ) );
+                
+        parseJSONPreferences( (HttpServletRequest) pageContext.getRequest(), prefs );
+
+        pageContext.getSession().setAttribute( SESSIONPREFS, prefs );        
+    }
+
+ 
+    /**
+     *  Parses new-style preferences stored as JSON objects and stores them
+     *  in the session.  Everything in the cookie is stored.
+     *  
+     *  @param request
+     *  @param prefs The default hashmap of preferences
+     *  
+     */
+    private static void parseJSONPreferences( HttpServletRequest request, Preferences prefs )
+    {
+        //FIXME: urlDecodeUTF8 should better go in HttpUtil ??
+        String prefVal = TextUtil.urlDecodeUTF8( HttpUtil.retrieveCookieValue( request, "JSPWikiUserPrefs" ) );
+        
+        if( prefVal != null )
+        {
+            try
+            {
+                JSONObject jo = new JSONObject( prefVal );
+    
+                for( Iterator i = jo.keys(); i.hasNext(); )
+                {
+                    String key = TextUtil.replaceEntities( (String)i.next() );
+                    prefs.put(key, jo.getString(key) );
+                }
+            }
+            catch( ParseException e )
+            {
+            }
+        }
+    }
+
+    /**
+     *  Returns a preference value programmatically.
+     *  FIXME
+     *  
+     *  @param wikiContext
+     *  @param name
+     *  @return
+     */
+    public static String getPreference( WikiContext wikiContext, String name )
+    {
+        Preferences prefs = (Preferences)wikiContext.getHttpRequest().getSession().getAttribute( SESSIONPREFS );
+        
+        if( prefs != null )
+            return (String)prefs.get( name );
+        
+        return null;
+    }
+    /**
+     *  Returns a preference value programmatically.
+     *  FIXME
+     *  
+     *  @param pageContext
+     *  @param name
+     *  @return
+     */
+    public static String getPreference( PageContext pageContext, String name )
+    {
+        Preferences prefs = (Preferences)pageContext.getSession().getAttribute( SESSIONPREFS );
+        
+        if( prefs != null )
+            return (String)prefs.get( name );
+        
+        return null;
+    }
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/providers/AbstractFileProvider.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/providers/AbstractFileProvider.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/providers/AbstractFileProvider.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/providers/AbstractFileProvider.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,433 @@
+/* 
+    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.providers;
+
+import java.io.*;
+import java.util.Properties;
+import java.util.Collection;
+import java.util.Date;
+import java.util.TreeSet;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.log4j.Logger;
+
+import com.ecyrd.jspwiki.*;
+
+/**
+ *  Provides a simple directory based repository for Wiki pages.
+ *  <P>
+ *  All files have ".txt" appended to make life easier for those
+ *  who insist on using Windows or other software which makes assumptions
+ *  on the files contents based on its name.
+ *  <p>
+ *  This class functions as a superclass to all file based providers.
+ *
+ *  @since 2.1.21.
+ *
+ *  @author Janne Jalkanen
+ */
+public abstract class AbstractFileProvider
+    implements WikiPageProvider
+{
+    private static final Logger   log = Logger.getLogger(AbstractFileProvider.class);
+    private String m_pageDirectory = "/tmp/";
+    
+    protected String m_encoding;
+    
+    protected WikiEngine m_engine;
+
+    /**
+     *  Name of the property that defines where page directories are.
+     */
+    public static final String      PROP_PAGEDIR = "jspwiki.fileSystemProvider.pageDir";
+
+    /**
+     *  All files should have this extension to be recognized as JSPWiki files.
+     *  We default to .txt, because that is probably easiest for Windows users,
+     *  and guarantees correct handling.
+     */
+    public static final String FILE_EXT = ".txt";
+
+    public static final String DEFAULT_ENCODING = "ISO-8859-1";
+
+    private boolean m_windowsHackNeeded = false;
+    
+    /**
+     *  @throws FileNotFoundException If the specified page directory does not exist.
+     *  @throws IOException In case the specified page directory is a file, not a directory.
+     */
+    public void initialize( WikiEngine engine, Properties properties )
+        throws NoRequiredPropertyException,
+               IOException
+    {
+        log.debug("Initing FileSystemProvider");
+        m_pageDirectory = WikiEngine.getRequiredProperty( properties, PROP_PAGEDIR );
+
+        File f = new File(m_pageDirectory);
+
+        if( !f.exists() )
+        {
+            f.mkdirs();
+        }
+        else if( !f.isDirectory() )
+        {
+            throw new IOException("Page directory is not a directory: "+m_pageDirectory);
+        }
+        
+        m_engine = engine;
+
+        m_encoding = properties.getProperty( WikiEngine.PROP_ENCODING, 
+                                             DEFAULT_ENCODING );
+
+        String os = System.getProperty( "os.name" ).toLowerCase();
+        
+        if( os.startsWith("windows") || os.equals("nt") )
+        {
+            m_windowsHackNeeded = true;
+        }
+        
+        log.info( "Wikipages are read from '" + m_pageDirectory + "'" );
+    }
+
+
+    String getPageDirectory()
+    {
+        return m_pageDirectory;
+    }
+
+    private static final String[] WINDOWS_DEVICE_NAMES =
+    {
+        "con", "prn", "nul", "aux", "lpt1", "lpt2", "lpt3", "lpt4", "lpt5", "lpt6", "lpt7", "lpt8", "lpt9",
+        "com1", "com2", "com3", "com4", "com5", "com6", "com7", "com8", "com9"
+    };
+    
+    /**
+     *  This makes sure that the queried page name
+     *  is still readable by the file system.
+     */
+    protected String mangleName( String pagename )
+    {
+        pagename = TextUtil.urlEncode( pagename, m_encoding );
+        
+        pagename = TextUtil.replaceString( pagename, "/", "%2F" );
+
+        if( m_windowsHackNeeded )
+        {
+            String pn = pagename.toLowerCase();
+            for( int i = 0; i < WINDOWS_DEVICE_NAMES.length; i++ )
+            {
+                if( WINDOWS_DEVICE_NAMES[i].equals(pn) )
+                {
+                    pagename = "$$$" + pagename;
+                }
+            }
+        }
+        
+        return pagename;
+    }
+
+    /**
+     *  This makes the reverse of mangleName.
+     */
+    protected String unmangleName( String filename )
+    {
+        // The exception should never happen.
+        try
+        {
+            if( m_windowsHackNeeded && filename.startsWith( "$$$") && filename.length() > 3 )
+            {
+                filename = filename.substring(3);
+            }
+            
+            return TextUtil.urlDecode( filename, m_encoding );
+        }
+        catch( UnsupportedEncodingException e ) 
+        {
+            throw new InternalWikiException("Faulty encoding; should never happen");
+        }
+    }
+    
+    /**
+     *  Finds a Wiki page from the page repository.
+     */
+    protected File findPage( String page )
+    {
+        return new File( m_pageDirectory, mangleName(page)+FILE_EXT );
+    }
+
+    
+    public boolean pageExists( String page )
+    {
+        File pagefile = findPage( page );
+
+        return pagefile.exists();        
+    }
+
+    /**
+     *  This implementation just returns the current version, as filesystem
+     *  does not provide versioning information for now.
+     */
+    public String getPageText( String page, int version )
+        throws ProviderException
+    {
+        return getPageText( page );
+    }
+
+    /**
+     *  Read the text directly from the correct file.
+     */
+    private String getPageText( String page )
+    {
+        String result  = null;
+        InputStream in = null;
+
+        File pagedata = findPage( page );
+
+        if( pagedata.exists() )
+        {
+            if( pagedata.canRead() )
+            {
+                try
+                {          
+                    in = new FileInputStream( pagedata );
+                    result = FileUtil.readContents( in, m_encoding );
+                }
+                catch( IOException e )
+                {
+                    log.error("Failed to read", e);
+                }
+                finally
+                {
+                    try
+                    {
+                        if( in  != null ) in.close();
+                    }
+                    catch( Exception e ) 
+                    {
+                        log.fatal("Closing failed",e);
+                    }
+                }
+            }
+            else
+            {
+                log.warn("Failed to read page '"+page+"' from '"+pagedata.getAbsolutePath()+"', possibly a permissions problem");
+            }
+        }
+        else
+        {
+            // This is okay.
+            log.info("New page '"+page+"'");
+        }
+
+        return result;
+    }
+
+    public void putPageText( WikiPage page, String text )        
+        throws ProviderException
+    {
+        File file = findPage( page.getName() );
+        PrintWriter out = null;
+
+        try
+        {
+            out = new PrintWriter(new OutputStreamWriter( new FileOutputStream( file ),
+                                                          m_encoding ));
+
+            out.print( text );
+        }
+        catch( IOException e )
+        {
+            log.error( "Saving failed" );
+        }
+        finally
+        {
+            if( out != null ) out.close();
+        }
+    }
+
+    public Collection getAllPages()
+        throws ProviderException
+    {
+        log.debug("Getting all pages...");
+
+        ArrayList set = new ArrayList();
+
+        File wikipagedir = new File( m_pageDirectory );
+
+        File[] wikipages = wikipagedir.listFiles( new WikiFileFilter() );
+
+        if( wikipages == null )
+        {
+            log.error("Wikipages directory '" + m_pageDirectory + "' does not exist! Please check " + PROP_PAGEDIR + " in jspwiki.properties.");
+            throw new InternalWikiException("Page directory does not exist");
+        }
+
+        for( int i = 0; i < wikipages.length; i++ )
+        {
+            String wikiname = wikipages[i].getName();
+            int cutpoint = wikiname.lastIndexOf( FILE_EXT );
+
+            WikiPage page = getPageInfo( unmangleName(wikiname.substring(0,cutpoint)),
+                                         WikiPageProvider.LATEST_VERSION );
+            if( page == null )
+            {
+                // This should not really happen.
+                // FIXME: Should we throw an exception here?
+                log.error("Page "+wikiname+" was found in directory listing, but could not be located individually.");
+                continue;
+            }
+            
+            set.add( page );
+        }
+
+        return set;        
+    }
+
+    public Collection getAllChangedSince( Date date )
+    {
+        return new ArrayList(); // FIXME
+    }
+
+    public int getPageCount()
+    {
+        File wikipagedir = new File( m_pageDirectory );
+
+        File[] wikipages = wikipagedir.listFiles( new WikiFileFilter() );
+
+        return wikipages.length;
+    }
+
+    /**
+     * Iterates through all WikiPages, matches them against the given query,
+     * and returns a Collection of SearchResult objects.
+     */
+    public Collection findPages( QueryItem[] query )
+    {
+        File wikipagedir = new File( m_pageDirectory );
+        TreeSet res = new TreeSet( new SearchResultComparator() );
+        SearchMatcher matcher = new SearchMatcher( m_engine, query );
+
+        File[] wikipages = wikipagedir.listFiles( new WikiFileFilter() );
+
+        for( int i = 0; i < wikipages.length; i++ )
+        {
+            FileInputStream input = null;
+
+            // log.debug("Searching page "+wikipages[i].getPath() );
+
+            String filename = wikipages[i].getName();
+            int cutpoint    = filename.lastIndexOf( FILE_EXT );
+            String wikiname = filename.substring( 0, cutpoint );
+
+            wikiname = unmangleName( wikiname );
+
+            try
+            {
+                input = new FileInputStream( wikipages[i] );
+                String pagetext = FileUtil.readContents( input, m_encoding );
+                SearchResult comparison = matcher.matchPageContent( wikiname, pagetext );
+                if( comparison != null )
+                {
+                    res.add( comparison );
+                }
+            }
+            catch( IOException e )
+            {
+                log.error( "Failed to read " + filename, e );
+            }
+            finally
+            {
+                try
+                {
+                    if( input != null ) input.close();
+                }
+                catch( IOException e ) {} // It's fine to fail silently.
+            }
+        }
+
+        return res;
+    }
+
+    /**
+     *  Always returns the latest version, since FileSystemProvider
+     *  does not support versioning.
+     */
+    public WikiPage getPageInfo( String page, int version )
+        throws ProviderException
+    {
+        File file = findPage( page );
+
+        if( !file.exists() )
+        {
+            return null;
+        }
+
+        WikiPage p = new WikiPage( m_engine, page );
+        p.setLastModified( new Date(file.lastModified()) );
+
+        return p;
+    }
+
+    /**
+     *  The FileSystemProvider provides only one version.
+     */
+    public List getVersionHistory( String page )
+        throws ProviderException
+    {
+        ArrayList list = new ArrayList();
+
+        list.add( getPageInfo( page, WikiPageProvider.LATEST_VERSION ) );
+
+        return list;
+    }
+
+    public String getProviderInfo()
+    {
+        return "";
+    }
+
+    public void deleteVersion( String pageName, int version )
+        throws ProviderException
+    {
+        if( version == WikiProvider.LATEST_VERSION )
+        {
+            File f = findPage( pageName );
+
+            f.delete();
+        }
+    }
+
+    public void deletePage( String pageName )
+        throws ProviderException
+    {
+        File f = findPage( pageName );
+
+        f.delete();
+    }
+
+    public static class WikiFileFilter
+        implements FilenameFilter
+    {
+        public boolean accept( File dir, String name )
+        {
+            return name.endsWith( FILE_EXT );
+        }
+    }
+}

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/providers/BasicAttachmentProvider.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/providers/BasicAttachmentProvider.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/providers/BasicAttachmentProvider.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/providers/BasicAttachmentProvider.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,698 @@
+/* 
+    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.providers;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.IOException;
+import java.io.FileNotFoundException;
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.FileOutputStream;
+import java.io.FileInputStream;
+
+import java.util.Collection;
+import java.util.Properties;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.Date;
+import java.util.List;
+import java.util.Collections;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.log4j.Logger;
+
+import com.ecyrd.jspwiki.*;
+import com.ecyrd.jspwiki.attachment.Attachment;
+
+/**
+ *  Provides basic, versioning attachments.
+ *
+ *  <PRE>
+ *   Structure is as follows:
+ *      attachment_dir/
+ *         ThisPage/
+ *            attachment.doc/
+ *               attachment.properties
+ *               1.doc
+ *               2.doc
+ *               3.doc
+ *            picture.png/
+ *               attachment.properties
+ *               1.png
+ *               2.png
+ *         ThatPage/
+ *            picture.png/
+ *               attachment.properties
+ *               1.png
+ *             
+ *  </PRE>
+ *
+ *  The names of the directories will be URLencoded.
+ *  <p>
+ *  "attachment.properties" consists of the following items:
+ *  <UL>
+ *   <LI>1.author = author name for version 1 (etc)
+ *  </UL>
+ */
+public class BasicAttachmentProvider
+    implements WikiAttachmentProvider
+{
+    private WikiEngine         m_engine;
+    private String             m_storageDir;
+    public static final String PROP_STORAGEDIR = "jspwiki.basicAttachmentProvider.storageDir";
+    
+    /*
+     * Disable client cache for files with patterns
+     * since 2.5.96
+     */
+    private Pattern            m_disableCache = null;
+    public static final String PROP_DISABLECACHE = "jspwiki.basicAttachmentProvider.disableCache";
+
+    public static final String PROPERTY_FILE   = "attachment.properties";
+
+    public static final String DIR_EXTENSION   = "-att";
+    public static final String ATTDIR_EXTENSION = "-dir";
+    
+    static final Logger log = Logger.getLogger( BasicAttachmentProvider.class );
+
+    public void initialize( WikiEngine engine, Properties properties ) 
+        throws NoRequiredPropertyException,
+               IOException
+    {
+        m_engine = engine;
+        m_storageDir = WikiEngine.getRequiredProperty( properties, PROP_STORAGEDIR );
+        
+        String patternString = engine.getWikiProperties().getProperty( PROP_DISABLECACHE );
+        if ( patternString != null )
+        {
+            m_disableCache = Pattern.compile(patternString);
+        }
+
+        //
+        //  Check if the directory exists - if it doesn't, create it.
+        //
+        File f = new File( m_storageDir );
+
+        if( !f.exists() )
+        {
+            f.mkdirs();
+        }
+
+        //
+        // Some sanity checks
+        //
+        if( !f.exists() ) 
+            throw new IOException("Could not find or create attachment storage directory '"+m_storageDir+"'");
+
+        if( !f.canWrite() ) 
+            throw new IOException("Cannot write to the attachment storage directory '"+m_storageDir+"'");
+        
+        if( !f.isDirectory() )
+            throw new IOException("Your attachment storage points to a file, not a directory: '"+m_storageDir+"'");
+    }
+
+    /**
+     *  Finds storage dir, and if it exists, makes sure that it is valid.
+     *
+     *  @param wikipage Page to which this attachment is attached.
+     */
+    private File findPageDir( String wikipage )
+        throws ProviderException
+    {
+        wikipage = mangleName( wikipage );
+
+        File f = new File( m_storageDir, wikipage+DIR_EXTENSION );
+
+        if( f.exists() && !f.isDirectory() )
+        {
+            throw new ProviderException("Storage dir '"+f.getAbsolutePath()+"' is not a directory!");
+        }
+
+        return f;
+    }
+
+    private static String mangleName( String wikiname )
+    {
+        String res = TextUtil.urlEncodeUTF8( wikiname );
+
+        return res;
+    }
+
+    private static String unmangleName( String filename )
+    {
+        return TextUtil.urlDecodeUTF8( filename );
+    }
+    
+    /**
+     *  Finds the dir in which the attachment lives.
+     */
+    private File findAttachmentDir( Attachment att )
+        throws ProviderException
+    {
+        File f = new File( findPageDir(att.getParentName()), 
+                           mangleName(att.getFileName()+ATTDIR_EXTENSION) );
+
+        //
+        //  Migration code for earlier versions of JSPWiki.
+        //  Originally, we used plain filename.  Then we realized we need
+        //  to urlencode it.  Then we realized that we have to use a
+        //  postfix to make sure illegal file names are never formed.
+        //
+        if( !f.exists() )
+        {
+            File oldf = new File( findPageDir( att.getParentName() ),
+                                  mangleName( att.getFileName() ) );
+            if( oldf.exists() )
+            {
+                f = oldf;
+            }
+            else
+            {
+                oldf = new File( findPageDir( att.getParentName() ),
+                                 att.getFileName() );
+
+                if( oldf.exists() )
+                {
+                    f = oldf;
+                }
+            }
+        }
+
+        return f;
+    }
+
+    /**
+     *  Goes through the repository and decides which version is
+     *  the newest one in that directory.
+     *
+     *  @return Latest version number in the repository, or 0, if
+     *          there is no page in the repository.
+     */
+    private int findLatestVersion( Attachment att )
+        throws ProviderException
+    {
+        // File pageDir = findPageDir( att.getName() );
+        File attDir  = findAttachmentDir( att );
+
+        // log.debug("Finding pages in "+attDir.getAbsolutePath());
+        String[] pages = attDir.list( new AttachmentVersionFilter() );
+
+        if( pages == null )
+        {
+            return 0; // No such thing found.
+        }
+
+        int version = 0;
+
+        for( int i = 0; i < pages.length; i++ )
+        {
+            // log.debug("Checking: "+pages[i]);
+            int cutpoint = pages[i].indexOf( '.' );
+                String pageNum = ( cutpoint > 0 ) ? pages[i].substring( 0, cutpoint ) : pages[i] ;
+
+                try
+                {
+                    int res = Integer.parseInt( pageNum );
+
+                    if( res > version )
+                    {
+                        version = res;
+                    }
+                }
+                catch( NumberFormatException e ) {} // It's okay to skip these.
+            }
+
+        return version;
+    }
+
+    /**
+     *  Returns the file extension.  For example "test.png" returns "png".
+     *  <p>
+     *  If file has no extension, will return "bin"
+     */
+    protected static String getFileExtension( String filename )
+    {
+        String fileExt = "bin";
+
+        int dot = filename.lastIndexOf('.');
+        if( dot >= 0 && dot < filename.length()-1 )
+        {
+            fileExt = mangleName( filename.substring( dot+1 ) );
+        }
+
+        return fileExt;
+    }
+
+    /**
+     *  Writes the page properties back to the file system.
+     *  Note that it WILL overwrite any previous properties.
+     */
+    private void putPageProperties( Attachment att, Properties properties )
+        throws IOException,
+               ProviderException
+    {
+        File attDir = findAttachmentDir( att );
+        File propertyFile = new File( attDir, PROPERTY_FILE );
+
+        OutputStream out = new FileOutputStream( propertyFile );
+
+        properties.store( out, 
+                          " JSPWiki page properties for "+
+                          att.getName()+
+                          ". DO NOT MODIFY!" );
+
+        out.close();
+    }
+
+    /**
+     *  Reads page properties from the file system.
+     */
+    private Properties getPageProperties( Attachment att )
+        throws IOException,
+               ProviderException
+    {
+        Properties props = new Properties();
+
+        File propertyFile = new File( findAttachmentDir(att), PROPERTY_FILE );
+
+        if( propertyFile.exists() )
+        {
+            InputStream in = new FileInputStream( propertyFile );
+
+            props.load(in);
+
+            in.close();
+        }
+        
+        return props;
+    }
+
+    public void putAttachmentData( Attachment att, InputStream data )
+        throws ProviderException,
+               IOException
+    {
+        OutputStream out = null;
+        File attDir = findAttachmentDir( att );
+
+        if(!attDir.exists())
+        {
+            attDir.mkdirs();
+        }
+
+        int latestVersion = findLatestVersion( att );
+
+        // System.out.println("Latest version is "+latestVersion);
+
+        try
+        {
+            int versionNumber = latestVersion+1;
+
+            File newfile = new File( attDir, versionNumber+"."+
+                                     getFileExtension(att.getFileName()) );
+
+            log.info("Uploading attachment "+att.getFileName()+" to page "+att.getParentName());
+            log.info("Saving attachment contents to "+newfile.getAbsolutePath());
+            out = new FileOutputStream(newfile);
+
+            FileUtil.copyContents( data, out );
+
+            out.close();
+
+            Properties props = getPageProperties( att );
+
+            String author = att.getAuthor();
+
+            if( author == null )
+            {
+                author = "unknown";
+            }
+
+            props.setProperty( versionNumber+".author", author );
+            
+            String changeNote = (String)att.getAttribute(WikiPage.CHANGENOTE);
+            if( changeNote != null )
+            {
+                props.setProperty( versionNumber+".changenote", changeNote );
+            }
+            
+            putPageProperties( att, props );
+        }
+        catch( IOException e )
+        {
+            log.error( "Could not save attachment data: ", e );
+            throw (IOException) e.fillInStackTrace();
+        }
+        finally
+        {
+            if( out != null ) out.close();
+        }
+    }
+
+    public String getProviderInfo()
+    {
+        return "";
+    }
+
+    private File findFile( File dir, Attachment att )
+        throws FileNotFoundException,
+               ProviderException
+    {
+        int version = att.getVersion();
+
+        if( version == WikiProvider.LATEST_VERSION )
+        {
+            version = findLatestVersion( att );
+        }
+
+        String ext = getFileExtension( att.getFileName() );
+        File f = new File( dir, version+"."+ext );
+
+        if( !f.exists() )
+        {
+            if ("bin".equals(ext))
+            {
+                File fOld = new File( dir, version+"." );
+                if (fOld.exists())
+                    f = fOld;
+            }
+            if( !f.exists() )
+            {
+                throw new FileNotFoundException("No such file: "+f.getAbsolutePath()+" exists.");
+            }
+        }
+
+        return f;
+    }
+
+    public InputStream getAttachmentData( Attachment att )
+        throws IOException,
+               ProviderException
+    {
+        File attDir = findAttachmentDir( att );
+
+        try
+        {
+            File f = findFile( attDir, att );
+
+            return new FileInputStream( f );
+        }
+        catch( FileNotFoundException e )
+        {
+            log.error("File not found: "+e.getMessage());
+            throw new ProviderException("No such page was found.");
+        }
+    }
+
+    public Collection listAttachments( WikiPage page )
+        throws ProviderException
+    {
+        Collection result = new ArrayList();
+
+        File dir = findPageDir( page.getName() );
+
+        if( dir != null )
+        {
+            String[] attachments = dir.list();
+
+            if( attachments != null )
+            {
+                //
+                //  We now have a list of all potential attachments in 
+                //  the directory.
+                //
+                for( int i = 0; i < attachments.length; i++ )
+                {
+                    File f = new File( dir, attachments[i] );
+
+                    if( f.isDirectory() )
+                    {
+                        String attachmentName = unmangleName( attachments[i] );
+
+                        //
+                        //  Is it a new-stylea attachment directory?  If yes,
+                        //  we'll just deduce the name.  If not, however,
+                        //  we'll check if there's a suitable property file
+                        //  in the directory.
+                        //
+                        if( attachmentName.endsWith( ATTDIR_EXTENSION ) )
+                        {
+                            attachmentName = attachmentName.substring( 0, attachmentName.length()-ATTDIR_EXTENSION.length() );
+                        }
+                        else
+                        {
+                            File propFile = new File( f, PROPERTY_FILE );
+
+                            if( !propFile.exists() )
+                            {
+                                //
+                                //  This is not obviously a JSPWiki attachment,
+                                //  so let's just skip it.
+                                //
+                                continue;
+                            }
+                        }
+
+                        Attachment att = getAttachmentInfo( page, attachmentName,
+                                                            WikiProvider.LATEST_VERSION );
+
+                        //
+                        //  Sanity check - shouldn't really be happening, unless
+                        //  you mess with the repository directly.
+                        //
+                        if( att == null )
+                        {
+                            throw new ProviderException("Attachment disappeared while reading information:"+
+                                                        " if you did not touch the repository, there is a serious bug somewhere. "+
+                                                        "Attachment = "+attachments[i]+
+                                                        ", decoded = "+attachmentName );
+                        }
+
+                        result.add( att );
+                    }
+                }
+            }
+        }
+
+        return result;
+    }
+
+    public Collection findAttachments( QueryItem[] query )
+    {
+        return null;
+    }
+
+    // FIXME: Very unoptimized.
+    public List listAllChanged( Date timestamp )
+        throws ProviderException
+    {
+        File attDir = new File( m_storageDir );
+
+        if( !attDir.exists() )
+        {
+            throw new ProviderException("Specified attachment directory "+m_storageDir+" does not exist!");
+        }
+
+        ArrayList list = new ArrayList();
+
+        String[] pagesWithAttachments = attDir.list( new AttachmentFilter() );
+
+        for( int i = 0; i < pagesWithAttachments.length; i++ )
+        {
+            String pageId = unmangleName( pagesWithAttachments[i] );
+            pageId = pageId.substring( 0, pageId.length()-DIR_EXTENSION.length() );
+            
+            Collection c = listAttachments( new WikiPage( m_engine, pageId ) );
+
+            for( Iterator it = c.iterator(); it.hasNext(); )
+            {
+                Attachment att = (Attachment) it.next();
+
+                if( att.getLastModified().after( timestamp ) )
+                {
+                    list.add( att );
+                }
+            }
+        }
+
+        Collections.sort( list, new PageTimeComparator() );
+
+        return list;
+    }
+
+    public Attachment getAttachmentInfo( WikiPage page, String name, int version )
+        throws ProviderException
+    {
+        Attachment att = new Attachment( m_engine, page.getName(), name );
+        File dir = findAttachmentDir( att );
+
+        if( !dir.exists() )
+        {
+            // log.debug("Attachment dir not found - thus no attachment can exist.");
+            return null;
+        }
+        
+        if( version == WikiProvider.LATEST_VERSION )
+        {
+            version = findLatestVersion(att);
+        }
+
+        att.setVersion( version );
+        
+        // Should attachment be cachable by the client (browser)?
+        if (m_disableCache != null) 
+        {
+            Matcher matcher = m_disableCache.matcher(name);
+            if (matcher.matches()) 
+            {
+                att.setCacheable(false);
+            }
+        }
+        
+
+        // System.out.println("Fetching info on version "+version);
+        try
+        {
+            Properties props = getPageProperties(att);
+
+            att.setAuthor( props.getProperty( version+".author" ) );
+
+            String changeNote = props.getProperty( version+".changenote" );
+            if( changeNote != null )
+            {
+                att.setAttribute(WikiPage.CHANGENOTE, changeNote);
+            }
+            
+            File f = findFile( dir, att );
+
+            att.setSize( f.length() );
+            att.setLastModified( new Date(f.lastModified()) );
+        }
+        catch( FileNotFoundException e )
+        {
+            return null;
+        }
+        catch( IOException e )
+        {
+            log.error("Can't read page properties", e );
+            throw new ProviderException("Cannot read page properties: "+e.getMessage());
+        }
+        // FIXME: Check for existence of this particular version.
+
+        return att;
+    }
+
+    public List getVersionHistory( Attachment att )
+    {
+        ArrayList list = new ArrayList();
+
+        try
+        {
+            int latest = findLatestVersion( att );
+
+            for( int i = latest; i >= 1; i-- )
+            {
+                Attachment a = getAttachmentInfo( new WikiPage( m_engine, att.getParentName() ), 
+                                                  att.getFileName(), i );
+
+                if( a != null )
+                {
+                    list.add( a );
+                }
+            }
+        }
+        catch( ProviderException e )
+        {
+            log.error("Getting version history failed for page: "+att,e);
+            // FIXME: SHould this fail?
+        }
+
+        return list;
+    }
+
+
+    public void deleteVersion( Attachment att )
+        throws ProviderException
+    {
+        // FIXME: Does nothing yet.
+    }
+
+    public void deleteAttachment( Attachment att )
+        throws ProviderException
+    {
+        File dir = findAttachmentDir( att );
+        String[] files = dir.list();
+
+        for( int i = 0; i < files.length; i++ )
+        {
+            File file = new File( dir.getAbsolutePath() + "/" + files[i] );
+            file.delete();
+        }
+        dir.delete();
+    }
+
+
+    /**
+     *  Returns only those directories that contain attachments.
+     */
+    public static class AttachmentFilter
+        implements FilenameFilter
+    {
+        public boolean accept( File dir, String name )
+        {
+            return name.endsWith( DIR_EXTENSION );
+        }
+    }
+
+    /**
+     *  Accepts only files that are actual versions, no control files.
+     */
+    public static class AttachmentVersionFilter
+        implements FilenameFilter
+    {
+        public boolean accept( File dir, String name )
+        {
+            return !name.equals( PROPERTY_FILE );
+        }
+    }
+
+    public void moveAttachmentsForPage( String oldParent, String newParent )
+        throws ProviderException
+    {
+        File srcDir = findPageDir( oldParent );
+        File destDir = findPageDir( newParent );
+
+        log.debug("Trying to move all attachments from "+srcDir+" to "+destDir);
+
+        // If it exists, we're overwriting an old page (this has already been
+        // confirmed at a higher level), so delete any existing attachments.
+        if (destDir.exists())
+        {
+            log.error("Page rename failed because target dirctory "+destDir+" exists");
+        }
+        else
+        {
+            //destDir.getParentFile().mkdir();
+            srcDir.renameTo(destDir);
+        }
+    }
+}
+

Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/providers/CachingAttachmentProvider.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/providers/CachingAttachmentProvider.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/providers/CachingAttachmentProvider.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/providers/CachingAttachmentProvider.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,376 @@
+/* 
+    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.providers;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.*;
+
+import org.apache.log4j.Logger;
+
+import com.ecyrd.jspwiki.*;
+import com.ecyrd.jspwiki.attachment.Attachment;
+import com.ecyrd.jspwiki.attachment.AttachmentManager;
+import com.ecyrd.jspwiki.util.ClassUtil;
+import com.opensymphony.oscache.base.Cache;
+import com.opensymphony.oscache.base.NeedsRefreshException;
+
+/**
+ *  Provides a caching attachment provider.  This class rests on top of a
+ *  real provider class and provides a cache to speed things up.  Only the
+ *  Attachment objects are cached; the actual attachment contents are 
+ *  fetched always from the provider.
+ *
+ *  @author Janne Jalkanen
+ *  @since 2.1.64.
+ */
+
+// FIXME: Do we need to clear the cache entry if we get an NRE and the attachment is not there?
+// FIXME: We probably clear the cache a bit too aggressively in places.
+// FIXME: Does not yet react well to external cache changes.  Should really use custom
+//        EntryRefreshPolicy for that.
+
+public class CachingAttachmentProvider
+    implements WikiAttachmentProvider
+{
+    private static final Logger log = Logger.getLogger(CachingAttachmentProvider.class);
+
+    private WikiAttachmentProvider m_provider;
+
+    /**
+     *  The cache contains Collection objects which contain Attachment objects.
+     *  The key is the parent wiki page name (String).
+     */
+    private Cache m_cache;
+
+    private long m_cacheMisses = 0;
+    private long m_cacheHits   = 0;
+
+    /** The extension to append to directory names to denote an attachment directory. */
+    public static final String DIR_EXTENSION   = "-att";
+    
+    /** Property that supplies the directory used to store attachments. */
+    public static final String PROP_STORAGEDIR = "jspwiki.basicAttachmentProvider.storageDir";
+
+    // FIXME: Make settable.
+    private int  m_refreshPeriod = 60*10; // 10 minutes at the moment
+
+    /**
+     * {@inheritDoc}
+     */
+    public void initialize( WikiEngine engine, Properties properties )
+        throws NoRequiredPropertyException,
+               IOException
+    {
+        log.debug("Initing CachingAttachmentProvider");
+
+        //
+        //  Construct an unlimited cache.
+        //
+        m_cache = new Cache( true, false, true );
+
+        //
+        //  Find and initialize real provider.
+        //
+        String classname = WikiEngine.getRequiredProperty( properties, 
+                                                           AttachmentManager.PROP_PROVIDER );
+        
+        try
+        {            
+            Class providerclass = ClassUtil.findClass( "com.ecyrd.jspwiki.providers",
+                                                       classname );
+
+            m_provider = (WikiAttachmentProvider)providerclass.newInstance();
+
+            log.debug("Initializing real provider class "+m_provider);
+            m_provider.initialize( engine, properties );
+        }
+        catch( ClassNotFoundException e )
+        {
+            log.error("Unable to locate provider class "+classname,e);
+            throw new IllegalArgumentException("no provider class");
+        }
+        catch( InstantiationException e )
+        {
+            log.error("Unable to create provider class "+classname,e);
+            throw new IllegalArgumentException("faulty provider class");
+        }
+        catch( IllegalAccessException e )
+        {
+            log.error("Illegal access to provider class "+classname,e);
+            throw new IllegalArgumentException("illegal provider class");
+        }
+
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void putAttachmentData( Attachment att, InputStream data )
+        throws ProviderException,
+               IOException
+    {
+        m_provider.putAttachmentData( att, data );
+
+        m_cache.flushEntry( att.getParentName() );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public InputStream getAttachmentData( Attachment att )
+        throws ProviderException,
+               IOException
+    {
+        return m_provider.getAttachmentData( att );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Collection listAttachments( WikiPage page )
+        throws ProviderException
+    {
+        log.debug("Listing attachments for "+page);
+        try
+        {
+            Collection c = (Collection)m_cache.getFromCache( page.getName(), m_refreshPeriod );
+
+            if( c != null )
+            {
+                log.debug("LIST from cache, "+page.getName()+", size="+c.size());
+                m_cacheHits++;
+                return cloneCollection(c);
+            }
+
+            log.debug("list NOT in cache, "+page.getName());
+
+            refresh( page );
+        }
+        catch( NeedsRefreshException nre )
+        {
+            try
+            {
+                Collection c = refresh( page );
+
+                return cloneCollection(c);
+            }
+            catch( Exception ex )
+            {
+                // Is a catch-all, because cache will get confused if
+                // we let this one go.
+                log.warn("Provider failed, returning cached content",ex);
+
+                m_cache.cancelUpdate(page.getName());
+                
+                return (Collection)nre.getCacheContent();
+            }
+        }
+
+        return new ArrayList();
+    }
+
+    private Collection cloneCollection( Collection c )
+    {
+        ArrayList list = new ArrayList();
+        
+        list.addAll( c );
+        
+        return list;
+    }
+    
+    /**
+     * {@inheritDoc}
+     */
+    public Collection findAttachments( QueryItem[] query )
+    {
+        return m_provider.findAttachments( query );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public List listAllChanged( Date timestamp )
+        throws ProviderException
+    {
+        // FIXME: Should cache
+        return m_provider.listAllChanged( timestamp );
+    }
+
+    /**
+     *  Simply goes through the collection and attempts to locate the
+     *  given attachment of that name.
+     *
+     *  @return null, if no such attachment was in this collection.
+     */
+    private Attachment findAttachmentFromCollection( Collection c, String name )
+    {
+        for( Iterator i = c.iterator(); i.hasNext(); )
+        {
+            Attachment att = (Attachment) i.next();
+
+            if( name.equals( att.getFileName() ) )
+            {
+                return att;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     *  Refreshes the cache content and updates counters.
+     *
+     *  @return The newly fetched object from the provider.
+     */
+    private final Collection refresh( WikiPage page )
+        throws ProviderException
+    {
+        m_cacheMisses++;
+        Collection c = m_provider.listAttachments( page );
+        m_cache.putInCache( page.getName(), c );
+
+        return c;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Attachment getAttachmentInfo( WikiPage page, String name, int version )
+        throws ProviderException
+    {
+        if( log.isDebugEnabled() )
+        {
+            log.debug("Getting attachments for "+page+", name="+name+", version="+version);
+        }
+
+        //
+        //  We don't cache previous versions
+        //
+        if( version != WikiProvider.LATEST_VERSION )
+        {       
+            log.debug("...we don't cache old versions");
+            return m_provider.getAttachmentInfo( page, name, version );
+        }
+
+        try
+        {
+            Collection c = (Collection)m_cache.getFromCache( page.getName(), m_refreshPeriod );
+            
+            if( c == null )
+            {
+                log.debug("...wasn't in the cache");
+                c = refresh( page );
+
+                if( c == null ) return null; // No such attachment
+            }
+            else
+            {
+                log.debug("...FOUND in the cache");
+                m_cacheHits++;
+            }
+
+            return findAttachmentFromCollection( c, name );
+
+        }
+        catch( NeedsRefreshException nre )
+        {
+            log.debug("...needs refresh");
+            Collection c = null;
+
+            try
+            {
+                c = refresh( page );
+            }
+            catch( Exception ex )
+            {
+                log.warn("Provider failed, returning cached content",ex);
+
+                m_cache.cancelUpdate( page.getName() );
+                c = (Collection)nre.getCacheContent();
+            }
+
+            if( c != null )
+            {
+                return findAttachmentFromCollection( c, name );
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public List getVersionHistory( Attachment att )
+    {
+        return m_provider.getVersionHistory( att );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void deleteVersion( Attachment att )
+        throws ProviderException
+    {
+        // This isn't strictly speaking correct, but it does not really matter
+        m_cache.putInCache( att.getParentName(), null );
+        m_provider.deleteVersion( att );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void deleteAttachment( Attachment att )
+        throws ProviderException
+    {
+        m_cache.putInCache( att.getParentName(), null );
+        m_provider.deleteAttachment( att );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public synchronized String getProviderInfo()
+    {              
+        return "Real provider: "+m_provider.getClass().getName()+
+               ".  Cache misses: "+m_cacheMisses+
+               ".  Cache hits: "+m_cacheHits;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public WikiAttachmentProvider getRealProvider()
+    {
+        return m_provider;
+    }
+    
+    /**
+     * {@inheritDoc}
+     */
+    public void moveAttachmentsForPage( String oldParent, String newParent )
+        throws ProviderException
+    {
+        m_provider.moveAttachmentsForPage(oldParent, newParent);
+        m_cache.putInCache( newParent, null ); // FIXME
+        m_cache.putInCache( oldParent, null );
+    }
+}



Mime
View raw message