You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@roller.apache.org by sn...@apache.org on 2005/10/21 23:46:28 UTC

svn commit: r327589 [43/72] - in /incubator/roller/branches/roller_1.x: ./ contrib/ contrib/lib/ contrib/plugins/ contrib/plugins/src/ contrib/plugins/src/org/ contrib/plugins/src/org/roller/ contrib/plugins/src/org/roller/presentation/ contrib/plugins...

Added: incubator/roller/branches/roller_1.x/src/org/roller/presentation/xmlrpc/MetaWeblogAPIHandler.java
URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_1.x/src/org/roller/presentation/xmlrpc/MetaWeblogAPIHandler.java?rev=327589&view=auto
==============================================================================
--- incubator/roller/branches/roller_1.x/src/org/roller/presentation/xmlrpc/MetaWeblogAPIHandler.java (added)
+++ incubator/roller/branches/roller_1.x/src/org/roller/presentation/xmlrpc/MetaWeblogAPIHandler.java Fri Oct 21 14:27:36 2005
@@ -0,0 +1,479 @@
+
+package org.roller.presentation.xmlrpc;
+
+import java.io.ByteArrayInputStream;
+import java.sql.Timestamp;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Vector;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.struts.util.RequestUtils;
+import org.apache.xmlrpc.XmlRpcException;
+import org.roller.RollerException;
+import org.roller.model.FileManager;
+import org.roller.model.Roller;
+import org.roller.model.WeblogManager;
+import org.roller.pojos.WeblogCategoryData;
+import org.roller.pojos.WeblogEntryData;
+import org.roller.pojos.WebsiteData;
+import org.roller.presentation.RollerContext;
+import org.roller.presentation.RollerRequest;
+import org.roller.util.RollerMessages;
+import org.roller.util.Utilities;
+
+
+/**
+ * Roller XML-RPC Handler for the MetaWeblog API.
+ *
+ * MetaWeblog API spec can be found at http://www.xmlrpc.com/metaWeblogApi
+ *
+ * @author David M Johnson
+ */
+public class MetaWeblogAPIHandler extends BloggerAPIHandler
+{
+    static final long serialVersionUID = -1364456614935668629L;
+    
+    private static Log mLogger =
+        LogFactory.getFactory().getInstance(MetaWeblogAPIHandler.class);
+
+    public MetaWeblogAPIHandler()
+    {
+        super();
+    }
+
+    //------------------------------------------------------------------------
+
+    /**
+     * Authenticates a user and returns the categories available in the website
+     *
+     * @param blogid Dummy Value for Roller
+     * @param userid Login for a MetaWeblog user who has permission to post to the blog
+     * @param password Password for said username
+     * @throws Exception
+     * @return
+     */
+    public Object getCategories(String blogid, String userid, String password)
+                         throws Exception
+    {
+        mLogger.info("getCategories() Called =====[ SUPPORTED ]=====");
+        mLogger.info("     BlogId: " + blogid);
+        mLogger.info("     UserId: " + userid);
+
+        WebsiteData website = validate(userid,password);
+        RollerRequest rreq = RollerRequest.getRollerRequest();
+        Roller roller = rreq.getRoller();
+        try
+        {
+            Hashtable result = new Hashtable();
+            WeblogManager weblogMgr = roller.getWeblogManager();
+            List cats = weblogMgr.getWeblogCategories(website, false);
+            for (Iterator wbcItr = cats.iterator(); wbcItr.hasNext();) {
+                WeblogCategoryData category = (WeblogCategoryData) wbcItr.next();
+                result.put(category.getPath(), createCategoryStruct(category));
+            }
+            return result;
+        }
+        catch (Exception e)
+        {
+            String msg = "ERROR in MetaWeblogAPIHandler.getCategories";
+            mLogger.error(msg,e);
+            throw new XmlRpcException(UNKNOWN_EXCEPTION, msg);
+        }
+    }
+
+    //------------------------------------------------------------------------
+
+    /**
+     * Edits a given post. Optionally, will publish the blog after making the edit
+     *
+     * @param postid Unique identifier of the post to be changed
+     * @param userid Login for a MetaWeblog user who has permission to post to the blog
+     * @param password Password for said username
+     * @param struct Contents of the post
+     * @param publish If true, the blog will be published immediately after the post is made
+     * @throws org.apache.xmlrpc.XmlRpcException
+     * @return
+     */
+    public boolean editPost(String postid, String userid, String password,
+                            Hashtable struct, boolean publish)
+                     throws Exception
+    {
+        mLogger.info("editPost() Called ========[ SUPPORTED ]=====");
+        mLogger.info("     PostId: " + postid);
+        mLogger.info("     UserId: " + userid);
+        mLogger.info("    Publish: " + publish);
+
+        validate(userid,password);
+
+        Roller roller = RollerRequest.getRollerRequest().getRoller();
+
+        Hashtable postcontent = struct;
+        String description = (String)postcontent.get("description");
+        String title = (String)postcontent.get("title");
+        if (title == null) title = "";
+
+        Date dateCreated = (Date)postcontent.get("dateCreated");
+        if (dateCreated == null) dateCreated = (Date)postcontent.get("pubDate");
+
+        String cat = null;
+        if ( postcontent.get("categories") != null )
+        {
+            Vector cats = (Vector)postcontent.get("categories");
+            cat = (String)cats.elementAt(0);
+        }
+        mLogger.info("      Title: " + title);
+        mLogger.info("   Category: " + cat);
+
+        try
+        {
+            WeblogManager weblogMgr = roller.getWeblogManager();
+
+            Timestamp current =
+                new Timestamp(System.currentTimeMillis());
+
+            WeblogEntryData entry =
+                weblogMgr.retrieveWeblogEntry(postid);
+
+            if ( !title.equals("") ) entry.setTitle(title);
+            entry.setText(description);
+            entry.setUpdateTime(current);
+            if (dateCreated != null) 
+            {
+                entry.setPubTime(new Timestamp(dateCreated.getTime()));
+            }
+            entry.setPublishEntry(Boolean.valueOf(publish));
+
+            if ( cat != null )
+            {
+                // Use first category specified by request
+                WeblogCategoryData cd = 
+                    weblogMgr.getWeblogCategoryByPath(entry.getWebsite(), cat);
+                entry.setCategory(cd);
+            }
+
+            entry.save();
+            roller.commit();
+            flushPageCache(userid);
+
+            // TODO: Roller timestamps need better than 1 second accuracy
+            // Until then, we can't allow more than one post per second
+            Thread.sleep(1000);
+            
+            return true;
+        }
+        catch (Exception e)
+        {
+            String msg = "ERROR in MetaWeblogAPIHandler.editPost";
+            mLogger.error(msg,e);
+            throw new XmlRpcException(UNKNOWN_EXCEPTION, msg);
+        }
+    }
+
+    //------------------------------------------------------------------------
+
+    /**
+     * Makes a new post to a designated blog. Optionally, will publish the blog after making the post
+     *
+     * @param blogid Unique identifier of the blog the post will be added to
+     * @param userid Login for a MetaWeblog user who has permission to post to the blog
+     * @param password Password for said username
+     * @param struct Contents of the post
+     * @param publish If true, the blog will be published immediately after the post is made
+     * @throws org.apache.xmlrpc.XmlRpcException
+     * @return
+     */
+    public String newPost(
+        String blogid, String userid, String password, Hashtable struct, boolean publish)
+        throws Exception
+    {
+        mLogger.info("newPost() Called ===========[ SUPPORTED ]=====");
+        mLogger.info("     BlogId: " + blogid);
+        mLogger.info("     UserId: " + userid);
+        mLogger.info("    Publish: " + publish);
+
+        WebsiteData website = validate(userid,password);
+
+        Hashtable postcontent = struct;
+        String description = (String)postcontent.get("description");
+        String title = (String)postcontent.get("title");
+        if (title == null) title = "";
+
+        Date dateCreated = (Date)postcontent.get("dateCreated");
+        if (dateCreated == null) dateCreated = (Date)postcontent.get("pubDate");
+        if (dateCreated == null) dateCreated = new Date();
+
+        String cat = null;
+        if ( postcontent.get("categories") != null )
+        {
+            Vector cats = (Vector)postcontent.get("categories");
+            if (cats.size() > 0) 
+            {
+                // only use the first category passed in
+                cat = (String)cats.elementAt(0);
+            }
+        }
+        mLogger.info("      Title: " + title);
+        mLogger.info("   Category: " + cat);
+
+        try
+        {
+            Roller roller = RollerRequest.getRollerRequest().getRoller();
+            WeblogManager weblogMgr = roller.getWeblogManager();
+
+            Timestamp current =
+                new Timestamp(System.currentTimeMillis());
+
+            WeblogEntryData entry = new WeblogEntryData();
+            entry.setTitle(title);
+            entry.setText(description);
+            entry.setPubTime(new Timestamp(dateCreated.getTime()));
+            entry.setUpdateTime(current);
+            entry.setWebsite(website);
+            entry.setPublishEntry(Boolean.valueOf(publish));
+
+            if ( cat != null )
+            {
+                // Use first category specified by request
+                WeblogCategoryData cd = 
+                    weblogMgr.getWeblogCategoryByPath(website, cat);
+                entry.setCategory(cd);
+            }
+            else
+            {
+                // Use Blogger API category from user's weblog config
+                entry.setCategory(website.getBloggerCategory());
+            }
+
+            entry.save();
+            roller.commit();
+            flushPageCache(userid);
+
+            // TODO: Roller timestamps need better than 1 second accuracy
+            // Until then, we can't allow more than one post per second
+            Thread.sleep(1000);
+
+            return entry.getId();
+        }
+        catch (Exception e)
+        {
+            String msg = "ERROR in MetaWeblogAPIHandler.newPost";
+            mLogger.error(msg,e);
+            throw new XmlRpcException(UNKNOWN_EXCEPTION, msg);
+        }
+    }
+
+    //------------------------------------------------------------------------
+
+    /**
+     *
+     * @param postid
+     * @param userid
+     * @param password
+     * @return
+     * @throws Exception
+     */
+    public Object getPost(String postid, String userid, String password)
+                   throws Exception
+    {
+        mLogger.info("getPost() Called =========[ SUPPORTED ]=====");
+        mLogger.info("     PostId: " + postid);
+        mLogger.info("     UserId: " + userid);
+
+        validate(userid,password);
+
+        try
+        {
+            Roller roller = RollerRequest.getRollerRequest().getRoller();
+            WeblogManager weblogMgr = roller.getWeblogManager();
+            WeblogEntryData entry = weblogMgr.retrieveWeblogEntry(postid);
+            return createPostStruct(entry);
+        }
+        catch (Exception e)
+        {
+            String msg = "ERROR in MetaWeblogAPIHandler.getPost";
+            mLogger.error(msg,e);
+            throw new XmlRpcException(UNKNOWN_EXCEPTION, msg);
+        }
+    }
+
+    //------------------------------------------------------------------------
+
+    /**
+     * Allows user to post a binary object, a file, to Roller. If the file is 
+     * allowed by the RollerConfig file-upload settings, then the file will be 
+     * placed in the user's upload diretory.
+     */
+    public Object newMediaObject(
+            String blogid, String userid, String password, Hashtable struct) 
+        throws Exception
+    {
+        mLogger.debug("newMediaObject() Called =[ SUPPORTED ]=====");
+        mLogger.debug("     BlogId: " + blogid);
+        mLogger.debug("     UserId: " + userid);
+        mLogger.debug("   Password: *********");
+
+        WebsiteData website = validate(userid, password);
+        try
+        {
+            String name = (String) struct.get("name");    
+            name = name.replaceAll("/","_");
+            String type = (String) struct.get("type");
+            mLogger.debug("newMediaObject name: " + name);
+            mLogger.debug("newMediaObject type: " + type);
+            
+            byte[] bits = (byte[]) struct.get("bits");
+
+            Roller roller = RollerRequest.getRollerRequest().getRoller();
+            FileManager fmgr = roller.getFileManager();
+            RollerMessages msgs = new RollerMessages();
+            
+            // If save is allowed by Roller system-wide policies
+            if (fmgr.canSave(website, name, bits.length, msgs)) 
+            {
+                // Then save the file
+                fmgr.saveFile(
+                    website, name, bits.length, new ByteArrayInputStream(bits));
+                
+                RollerRequest rreq = RollerRequest.getRollerRequest();
+                HttpServletRequest request = rreq.getRequest();
+                
+                // TODO: build URL to uploaded file should be done in FileManager
+                String uploadPath = RollerContext.getUploadPath(
+                        request.getSession(true).getServletContext());
+                uploadPath += "/" + website.getUser().getUserName() + "/" + name;
+                String fileLink = RequestUtils.printableURL(
+                        RequestUtils.absoluteURL(request, uploadPath));
+                
+                Hashtable returnStruct = new Hashtable(1);
+                returnStruct.put("url", fileLink);
+                return returnStruct;
+            }
+            throw new XmlRpcException(UPLOAD_DENIED_EXCEPTION, 
+                "File upload denied because:" + msgs.toString());
+        }
+        catch (RollerException e)
+        {
+            String msg = "ERROR in BlooggerAPIHander.newMediaObject";
+            mLogger.error(msg,e);
+            throw new XmlRpcException(UNKNOWN_EXCEPTION, msg);
+        }
+    }
+
+    /**
+     * Get a list of recent posts for a category
+     *
+     * @param blogid Unique identifier of the blog the post will be added to
+     * @param userid Login for a Blogger user who has permission to post to the blog
+     * @param password Password for said username
+     * @param numposts Number of Posts to Retrieve
+     * @throws XmlRpcException
+     * @return
+     */
+    public Object getRecentPosts(
+        String blogid, String userid, String password, int numposts)
+        throws Exception
+    {
+        mLogger.info("getRecentPosts() Called ===========[ SUPPORTED ]=====");
+        mLogger.info("     BlogId: " + blogid);
+        mLogger.info("     UserId: " + userid);
+        mLogger.info("     Number: " + numposts);
+
+        WebsiteData website = validate(userid,password);
+
+        try
+        {
+            Vector results = new Vector();
+
+            Roller roller = RollerRequest.getRollerRequest().getRoller();
+            WeblogManager weblogMgr = roller.getWeblogManager();
+            if (website != null)
+            {
+                Map entries = weblogMgr.getWeblogEntryObjectMap(
+                                website,                // userName
+                                null,                   // startDate
+                                new Date(),             // endDate
+                                null,                   // catName
+                                WeblogManager.ALL,      // status
+                                new Integer(numposts)); // maxEntries 
+                
+                Iterator iter = entries.values().iterator();
+                while (iter.hasNext())
+                {
+                    ArrayList list = (ArrayList) iter.next();
+                    Iterator entryIter = list.iterator();
+                    while (entryIter.hasNext())
+                    {
+                        WeblogEntryData entry = (WeblogEntryData)entryIter.next();
+                        results.addElement(createPostStruct(entry));
+                    }
+                }
+            }
+            return results;
+        }
+        catch (Exception e)
+        {
+            String msg = "ERROR in BlooggerAPIHander.getRecentPosts";
+            mLogger.error(msg,e);
+            throw new XmlRpcException(UNKNOWN_EXCEPTION, msg);
+        }
+    }
+    
+    private Hashtable createPostStruct(WeblogEntryData entry)
+    {       
+        RollerRequest rreq = RollerRequest.getRollerRequest();
+        HttpServletRequest request = rreq.getRequest();
+        RollerContext rollerCtx = RollerContext.getRollerContext(request);
+        String permalink = 
+            rollerCtx.getAbsoluteContextUrl(request) + entry.getPermaLink();
+        
+        Hashtable struct = new Hashtable();       
+        struct.put("title", entry.getTitle());
+        if (entry.getLink() != null) 
+        {
+            struct.put("link", Utilities.escapeHTML(entry.getLink()));
+        }
+        
+        struct.put("description", entry.getText());
+        struct.put("pubDate", entry.getPubTime());
+        struct.put("dateCreated", entry.getPubTime());
+        struct.put("guid", Utilities.escapeHTML(permalink));
+        struct.put("permaLink", Utilities.escapeHTML(permalink));
+        struct.put("postid", entry.getId());                        
+        struct.put("userid", entry.getWebsite().getUser().getId());
+
+        Vector catArray = new Vector();
+        catArray.addElement(entry.getCategory().getPath());      
+        struct.put("categories", catArray);
+        
+        return struct;
+    }
+    
+    private Hashtable createCategoryStruct(WeblogCategoryData category)
+    {
+        RollerRequest rreq = RollerRequest.getRollerRequest();
+        HttpServletRequest req = rreq.getRequest();
+        String contextUrl = RollerContext.getRollerContext(req).getAbsoluteContextUrl(req);
+        String userid = category.getWebsite().getUser().getId();
+
+        Hashtable struct = new Hashtable();
+        struct.put("description", category.getPath());
+        
+        String catUrl = contextUrl+"/page/"+userid+"?catname="+category.getPath();
+        catUrl = Utilities.stringReplace(catUrl," ","%20");
+        struct.put("htmlUrl", catUrl);
+
+        String rssUrl = contextUrl+"/rss/"+userid+"?catname="+category.getPath();
+        rssUrl = Utilities.stringReplace(catUrl," ","%20");
+        struct.put("rssUrl",rssUrl);
+        
+        return struct;
+    }
+}

Added: incubator/roller/branches/roller_1.x/src/org/roller/presentation/xmlrpc/RollerXMLRPCServlet.java
URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_1.x/src/org/roller/presentation/xmlrpc/RollerXMLRPCServlet.java?rev=327589&view=auto
==============================================================================
--- incubator/roller/branches/roller_1.x/src/org/roller/presentation/xmlrpc/RollerXMLRPCServlet.java (added)
+++ incubator/roller/branches/roller_1.x/src/org/roller/presentation/xmlrpc/RollerXMLRPCServlet.java Fri Oct 21 14:27:36 2005
@@ -0,0 +1,75 @@
+/*
+ * RollerXMLRPCServlet.java
+ */
+
+package org.roller.presentation.xmlrpc;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.xmlrpc.XmlRpcServer;
+
+import java.io.OutputStream;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Roller's XML RPC Servlet sets up XmlRpcHandler for Blogger/ API.
+ * 
+ * @author David M Johnson
+ * 
+ * @web.servlet name="RollerXMLRPCServlet"
+ * @web.servlet-mapping url-pattern="/xmlrpc"
+ */
+public class RollerXMLRPCServlet extends HttpServlet
+{
+    static final long serialVersionUID = -4424719615968330852L;
+    
+    private static Log mLogger = 
+        LogFactory.getFactory().getInstance(RollerXMLRPCServlet.class);
+        
+    private transient XmlRpcServer mXmlRpcServer = new XmlRpcServer();
+    private BloggerAPIHandler mBloggerHandler = null;
+    private MetaWeblogAPIHandler mMetaWeblogHandler = null;
+
+    //------------------------------------------------------------------------
+    
+    /** 
+     * Initializes the servlet.
+     */
+    public void init(ServletConfig config) throws ServletException
+    {
+        super.init(config);
+        try
+        {
+            mBloggerHandler = new BloggerAPIHandler();
+            mXmlRpcServer.addHandler("blogger", mBloggerHandler);
+            
+            mMetaWeblogHandler = new MetaWeblogAPIHandler();
+            mXmlRpcServer.addHandler("metaWeblog", mMetaWeblogHandler);
+        }
+        catch (Exception e)
+        {
+            mLogger.error("Initialization of XML-RPC servlet failed", e);
+        }
+    }
+
+    //------------------------------------------------------------------------
+    
+    protected void service(HttpServletRequest request,
+                                  HttpServletResponse response)
+        throws ServletException, java.io.IOException
+    {
+        byte[] result = mXmlRpcServer.execute(request.getInputStream());
+
+        response.setContentType("text/xml");
+        response.setContentLength(result.length);
+
+        OutputStream output = response.getOutputStream();
+        output.write(result);
+        output.flush();
+    }
+}

Added: incubator/roller/branches/roller_1.x/src/org/roller/presentation/xmlrpc/package.html
URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_1.x/src/org/roller/presentation/xmlrpc/package.html?rev=327589&view=auto
==============================================================================
--- incubator/roller/branches/roller_1.x/src/org/roller/presentation/xmlrpc/package.html (added)
+++ incubator/roller/branches/roller_1.x/src/org/roller/presentation/xmlrpc/package.html Fri Oct 21 14:27:36 2005
@@ -0,0 +1,16 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+  <title></title>
+</head>
+<body>
+Roller implementation of Blogger and MetaWeblog APIs.
+
+<p>Blogger API spec can be found at http://plant.blogger.com/api/index.html</p>
+
+<p>See also http://xmlrpc.free-conversant.com/docs/bloggerAPI</p>
+
+<p>MetaWeblog API spec can be found at http://www.xmlrpc.com/metaWeblogApi</p>
+
+</body>
+</html>

Added: incubator/roller/branches/roller_1.x/src/org/roller/util/Blacklist.java
URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_1.x/src/org/roller/util/Blacklist.java?rev=327589&view=auto
==============================================================================
--- incubator/roller/branches/roller_1.x/src/org/roller/util/Blacklist.java (added)
+++ incubator/roller/branches/roller_1.x/src/org/roller/util/Blacklist.java Fri Oct 21 14:27:36 2005
@@ -0,0 +1,440 @@
+/*
+ * Created on Nov 11, 2003
+ */
+package org.roller.util;
+
+import org.roller.util.StringUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Based on the list provided by Jay Allen for
+ * MT-Blacklist:
+ * http://www.jayallen.org/projects/mt-blacklist/
+ * 
+ * Will provide response whether submitted string
+ * contains an item listed in the supplied blacklist.
+ * This implementation does not do everything
+ * MT-Blacklist does, such as the "Search &amp; De-spam mode".
+ * 
+ * @author lance
+ */
+public class Blacklist
+{
+    private static Log mLogger = LogFactory.getLog(Blacklist.class);
+
+    private static Blacklist blacklist;
+    
+    public  static final String blacklistFile = "blacklist.txt";
+    private static final String blacklistURL = "http://www.jayallen.org/comment_spam/blacklist.txt";
+    private static final String lastUpdateStr = "Last update:";
+
+    // Default location of blacklist file (relative to realPath) in case that uploadDir is null or empty
+    // and realPath is non-null.
+    private static final String DEFAULT_BLACKLIST_DIR = "resources";
+    private String realPath;
+    private String uploadDir;
+
+    private List blacklistStr = new LinkedList();
+    private List blacklistRegex = new LinkedList();
+    
+    private Date ifModifiedSince = null;
+
+    /**
+     * Singleton factory method.
+     */
+    public static Blacklist getBlacklist(String realPath, String uploadDir)
+    {
+        if (blacklist == null)
+        {
+            Blacklist temp = new Blacklist(realPath, uploadDir);
+            temp.extractFromFile();
+            blacklist = temp;
+        }
+        return blacklist;
+    }
+    
+    /**
+     * This will try to download a new set of Blacklist
+     * rules.  If no change has occurred then return
+     * current Blacklist.
+     * 
+     * @return New Blacklist if rules have changed,
+     * otherwise return current Blacklist.
+     */
+    public static void checkForUpdate()
+    {
+        blacklist = blacklist.extractFromURL();
+    }
+
+    /**
+     * Hide constructor
+     */
+    private Blacklist(String realPath, String uploadDir)
+    {
+        this.realPath = realPath;
+        this.uploadDir = uploadDir;
+    }
+    
+    /**
+     * Read a local file for Blacklist rules.
+     */
+    private void extractFromFile()
+    {
+        InputStream txtStream = getFileInputStream();
+        if (txtStream != null)
+        {
+            readFromStream(txtStream, false);
+        }
+        else
+        {
+            throw new NullPointerException("Unable to load blacklist.txt.  " +
+            "Make sure blacklist.txt is in classpath.");
+        }    
+    }
+
+    /**
+     * Read in the InputStream for rules.
+     * @param txtStream
+     */
+    private String readFromStream(InputStream txtStream, boolean saveStream)
+    {
+        String line;
+        StringBuffer buf = new StringBuffer();
+        BufferedReader in = null;
+        try
+        {
+            in = new BufferedReader( 
+                new InputStreamReader( txtStream, "UTF-8" ) );
+            while ((line = in.readLine()) != null)
+            {
+                if (line.startsWith("#"))
+                {
+                    readComment(line);
+                }
+                else
+                {
+                    readRule(line);
+                }
+                
+                if (saveStream) buf.append(line).append("\n");
+            }
+        }
+        catch (Exception e)
+        {
+            mLogger.error(e);
+        }
+        finally
+        {
+           try
+            {
+                 if (in != null) in.close();
+            }
+            catch (IOException e1)
+            {
+                mLogger.error(e1);
+            }
+        }
+        return buf.toString();
+    }
+    
+    /**
+     * Connect to the web for blacklist.  Check to
+     * see if a newer version exists before parsing.
+     */
+    private Blacklist extractFromURL()
+    {
+        // now see if we can update it from the web
+        Blacklist oldBlacklist = getBlacklist(realPath, uploadDir);
+        Blacklist newBlacklist = new Blacklist(realPath, uploadDir);
+        try
+        {
+            URL url = new URL(blacklistURL);
+            HttpURLConnection connection = (HttpURLConnection)url.openConnection();
+            if (oldBlacklist.ifModifiedSince != null)
+            {
+                connection.setRequestProperty("If-Modified-Since",
+                                              DateUtil.formatRfc822(oldBlacklist.ifModifiedSince));
+            }
+
+            // did the connection return NotModified? If so, no need to parse
+            if ( connection.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED)
+            {
+                // we already have a current blacklist
+                return oldBlacklist;
+            }
+
+            // did the connection return a LastModified header?
+            long lastModifiedLong = connection.getHeaderFieldDate("Last-Modified", -1);
+
+            // if no ifModifiedSince, or lastModifiedLong is newer, then read stream
+            if (oldBlacklist.ifModifiedSince == null ||
+                oldBlacklist.ifModifiedSince.getTime() < lastModifiedLong)
+            {
+                String results = newBlacklist.readFromStream( connection.getInputStream(), true );
+
+                // save the new blacklist
+                newBlacklist.writeToFile(results);
+
+                if (newBlacklist.ifModifiedSince == null && lastModifiedLong != -1)
+                {
+                    newBlacklist.ifModifiedSince = new Date(lastModifiedLong);
+                }
+
+                return newBlacklist;
+            }
+        }
+        catch (Exception e)
+        {
+            // Catch all exceptions and just log at INFO (should this be WARN?) without a full stacktrace.
+            mLogger.info("Roller Blacklist Update: Unable to update comment spam blacklist due to exception: " + e);
+        }
+        return oldBlacklist;
+    }
+
+    /**
+     * @param str
+     */
+    private void readRule(String str)
+    {
+        if (StringUtils.isEmpty(str)) return; // bad condition
+        
+        String rule = str.trim();
+        
+        if (str.indexOf("#") > 0) // line has a comment
+        {
+            int commentLoc = str.indexOf("#");
+            rule = str.substring(0, commentLoc-1).trim(); // strip comment
+        }
+        
+        if (rule.indexOf( "(" ) > -1) // regex rule
+        {
+            // pre-compile patterns since they will be frequently used
+            blacklistRegex.add(Pattern.compile(rule));
+        }
+        else if (StringUtils.isNotEmpty(rule))
+        {    
+            blacklistStr.add(rule);
+        }
+    }
+
+    /**
+     * Try to parse out "Last update" value: 2004/03/08 23:17:30.
+     * @param str
+     */
+    private void readComment(String str)
+    {
+        int lastUpdatePos = str.indexOf(lastUpdateStr);
+        if (lastUpdatePos > -1)
+        {
+            str = str.substring(lastUpdatePos + lastUpdateStr.length());
+            str = str.trim();
+            try
+            {
+                SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
+                ifModifiedSince = DateUtil.parse(str, sdf);
+            }
+            catch (ParseException e)
+            {
+                mLogger.debug("ParseException reading " + str);
+            }
+        }
+    }
+
+    /**
+     * Does the String argument match any of the rules in the blacklist?
+     * 
+     * @param str
+     * @return
+     */
+    public boolean isBlacklisted(String str)
+    {
+        if (str == null || StringUtils.isEmpty(str)) return false;
+        
+        // First iterate over blacklist, doing indexOf.
+        // Then iterate over blacklistRegex and test.
+        // As soon as there is a hit in either case return true 
+        
+        // test plain String.indexOf
+        if( testStringRules(str) ) return true;
+        
+        // test regex blacklisted
+        return testRegExRules(str);
+    }
+    
+    /**
+     * Test String against the RegularExpression rules.
+     * 
+     * @param str
+     * @return
+     */
+    private boolean testRegExRules(String str)
+    {
+        boolean hit = false;
+        Pattern testPattern = null;
+        Iterator iter = blacklistRegex.iterator();
+        while (iter.hasNext())
+        {
+            testPattern = (Pattern)iter.next();
+            
+            // want to see what it is matching on
+            // if we are in "debug mode"
+            if (mLogger.isDebugEnabled())
+            {
+                Matcher matcher = testPattern.matcher(str);
+                if (matcher.find())
+                {
+                    mLogger.debug(matcher.group() + " matched by " + testPattern.pattern());
+                    hit = true;
+                    break;
+                }
+            }
+            else
+            {
+                if (testPattern.matcher(str).find())
+                {
+                    hit = true;
+                    break;
+                }
+            }
+        }
+        return hit;
+    }
+
+    /**
+     * Test the String against the String rules,
+     * using simple indexOf.
+     * 
+     * @param str
+     * @return
+     */
+    private boolean testStringRules(String str)
+    {
+        String test;
+        Iterator iter = blacklistStr.iterator();
+        boolean hit = false;
+        while (iter.hasNext())
+        {
+            test = (String)iter.next();
+            //System.out.println("check against |" + test + "|");
+            if (str.indexOf(test) > -1)
+            {
+                // want to see what it is matching on
+                if (mLogger.isDebugEnabled())
+                {
+                    mLogger.debug("matched:" + test + ":");
+                }
+                hit = true;
+                break;
+            }
+        }
+        return hit;
+    }
+    
+    /**
+     * Try reading blacklist.txt from wherever RollerConfig.getUploadDir()
+     * is, otherwise try loading it from web resource (/WEB-INF/).
+     */
+    private InputStream getFileInputStream()
+    {
+        try
+        {
+            // TODO: clean up
+            // This was previously throwing an NPE to get to the exception case 
+            // when being called in several places with indexDir==null. 
+            // This is just about as bad; it needs to be cleaned up.
+            String path = getBlacklistFilePath();
+            if (path == null)
+            {
+                throw new FileNotFoundException(
+                        "null path (indexDir and realPath both null)");
+            }
+            return new FileInputStream( path );
+        }
+        catch (Exception e)
+        {
+            return getClass().getResourceAsStream("/"+blacklistFile);
+        }
+    }
+
+    /**
+     * @param results
+     */
+    private void writeToFile(String results)
+    {
+        FileWriter out = null;
+        String path = getBlacklistFilePath();
+        if (path == null)
+        {
+            mLogger.debug("Not writing blacklist file since directory paths were null.");
+            return;
+        }
+        try
+        {
+            // attempt writing results
+            out = new FileWriter(path);
+            out.write( results.toCharArray() );
+        }
+        catch (Exception e)
+        {
+            mLogger.info("Unable to write new " + path);
+        }
+        finally
+        {
+            try
+            {
+                if (out != null) out.close();
+            }
+            catch (IOException e)
+            {
+                mLogger.error("Unable to close stream to " + path);
+            }
+        }
+    }
+
+    // Added for ROL-612 - TODO: Consider refactoring - nearly duplicate code in FileManagerImpl.
+    private String getBlacklistFilePath() 
+    {
+        if (uploadDir == null && realPath==null) 
+        {
+            // to preserve existing behavior forced to interpret this differently
+            return null;
+        }
+        if (uploadDir == null || uploadDir.trim().length() == 0) 
+        {
+            uploadDir = realPath + File.separator + DEFAULT_BLACKLIST_DIR;
+        }
+        return uploadDir + File.separator + blacklistFile;
+    }
+
+    /**
+     * Return pretty list of String and RegEx rules.
+     */
+    public String toString()
+    {
+        StringBuffer buf = new StringBuffer("blacklist ");
+        buf.append(blacklistStr).append("\n");
+        buf.append("Regex blacklist ").append(blacklistRegex);
+        return buf.toString();
+    }
+}

Added: incubator/roller/branches/roller_1.x/src/org/roller/util/CommentSpamChecker.java
URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_1.x/src/org/roller/util/CommentSpamChecker.java?rev=327589&view=auto
==============================================================================
--- incubator/roller/branches/roller_1.x/src/org/roller/util/CommentSpamChecker.java (added)
+++ incubator/roller/branches/roller_1.x/src/org/roller/util/CommentSpamChecker.java Fri Oct 21 14:27:36 2005
@@ -0,0 +1,90 @@
+package org.roller.util;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.roller.business.ThreadManagerImpl;
+import org.roller.model.RollerFactory;
+import org.roller.model.ThreadManager;
+import org.roller.pojos.CommentData;
+
+/**
+ * Created on Mar 9, 2004
+ * @author lance.lavandowska
+ */
+public class CommentSpamChecker
+{
+    private static Log mLogger = LogFactory.getLog(CommentSpamChecker.class);
+    private Blacklist blacklist = Blacklist.getBlacklist(null,null);
+
+    // -----------------------------------------------------------------------
+    /**
+     * Runs comment check on comment, sets spam flag on comment.
+     */
+    public void testComment(CommentData comment)
+    {
+        try
+        {
+            // by using the OR conditional it'll test each
+            // one in order and fall into the body without
+            // having to test each and every condition.
+            // Not sure which is the optimal order to check, though.
+            boolean isSpam = blacklist.isBlacklisted(comment.getUrl());
+            isSpam = blacklist.isBlacklisted(comment.getContent())?true:isSpam;
+            isSpam = blacklist.isBlacklisted(comment.getEmail())?true:isSpam;
+
+            if (isSpam)
+            {
+                comment.setSpam(Boolean.TRUE);
+                comment.save();
+                RollerFactory.getRoller().commit();
+            }
+        }
+        catch (Exception e)
+        {
+            mLogger.error("Processing Comment",e);
+        }
+        finally
+        {
+            RollerFactory.getRoller().release();
+        }
+    }
+    
+    // -----------------------------------------------------------------------
+    /**
+     * Spawns thread to run comment check.
+     */
+    public void testComment(CommentData comment, ThreadManager threadMgr)
+    {
+        try
+        {
+            if (threadMgr != null)
+            {
+                threadMgr.executeInBackground(new CommentCheckerRunnable(comment));
+            }
+            else
+            {
+                mLogger.warn("No thread manager found.");
+            }
+        } 
+        catch (InterruptedException e) {
+            mLogger.warn("Interrupted during Comment Spam check",e);
+        }
+    }
+
+    // -----------------------------------------------------------------------
+    /**
+     * Runnable to run spam check on it's own thread.
+     */
+    private class CommentCheckerRunnable implements Runnable
+    {
+        private CommentData mComment = null;
+        public CommentCheckerRunnable( CommentData comment)
+        {
+            mComment = comment;
+        }
+        public void run()
+        {
+            testComment(mComment);
+        }
+    }
+}
\ No newline at end of file

Added: incubator/roller/branches/roller_1.x/src/org/roller/util/DateUtil.java
URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_1.x/src/org/roller/util/DateUtil.java?rev=327589&view=auto
==============================================================================
--- incubator/roller/branches/roller_1.x/src/org/roller/util/DateUtil.java (added)
+++ incubator/roller/branches/roller_1.x/src/org/roller/util/DateUtil.java Fri Oct 21 14:27:36 2005
@@ -0,0 +1 @@
+package org.roller.util;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;

/**
 * General purpose date utilities.
 * @author Mark Saarinen
 * @author Lance Lavandowska
 */
public abstract class DateUtil extends Object
{
    public static final long millisInDay = 86400000;

    // some static date formats
    private static SimpleDateFormat[] mDateFormats = loadDateFormats();
    
    private static final SimpleDateFormat mFormat8chars = 
        new SimpleDateFormat("yyyyMMdd");

    private static final SimpleDateFormat mFormatIso8601Day = 
        new SimpleDateFormat("yyyy-MM-dd");

    private static final SimpleDateFormat mFormatIso8601 = 
        new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
    
    // http://www.w3.org/Protocols/rfc822/Overview.html#z28
    // Using Locale.US to fix ROL-725 and ROL-628
    private static final SimpleDateFormat mFormatRfc822 = 
        new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z", Locale.US); 

    private static SimpleDateFormat[] loadDateFormats()
    {
        SimpleDateFormat[] temp = {
            //new SimpleDateFormat("MM/dd/yyyy hh:mm:ss.SSS a"),
            new SimpleDateFormat("EEE MMM d HH:mm:ss z yyyy"), // standard Date.toString() results
            new SimpleDateFormat("M/d/yy hh:mm:ss"),
            new SimpleDateFormat("M/d/yyyy hh:mm:ss"),
            new SimpleDateFormat("M/d/yy hh:mm a"),
            new SimpleDateFormat("M/d/yyyy hh:mm a"),
            new SimpleDateFormat("M/d/yy HH:mm"),
            new SimpleDateFormat("M/d/yyyy HH:mm"),
            new SimpleDateFormat("dd.MM.yyyy HH:mm:ss"),
            new SimpleDateFormat("yy-MM-dd HH:mm:ss.SSS"),
            new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"), // standard Timestamp.toString() results
            new SimpleDateFormat("M-d-yy HH:mm"),
            new SimpleDateFormat("M-d-yyyy HH:mm"),
            new SimpleDateFormat("MM/dd/yyyy HH:mm:ss.SSS"),
            new SimpleDateFormat("M/d/yy"),
            new SimpleDateFormat("M/d/yyyy"),
            new SimpleDateFormat("M-d-yy"),
            new SimpleDateFormat("M-d-yyyy"),
            new SimpleDateFormat("MMMM d, yyyyy"),
            new SimpleDateFormat("MMM d, yyyyy")
        };
    
        return temp;
    }
    //-----------------------------------------------------------------------
    /**
     * Gets the array of SimpleDateFormats that DateUtil knows about.
    **/
    private static SimpleDateFormat[] getFormats()
    {
        return mDateFormats;
    }

	//-----------------------------------------------------------------------
	/**
	 * Returns a Date set to the last possible millisecond of the day, just
	 * before midnight. If a null day is passed in, a new Date is created.
	 * midnight (00m 00h 00s)
	 */
	public static Date getEndOfDay(Date day)
	{
		return getEndOfDay(day,Calendar.getInstance());
	}
	public static Date getEndOfDay(Date day,Calendar cal)
	{
		if (day == null) day = new Date();
		cal.setTime(day);
		cal.set(Calendar.HOUR_OF_DAY, cal.getMaximum(Calendar.HOUR_OF_DAY));
		cal.set(Calendar.MINUTE,      cal.getMaximum(Calendar.MINUTE));
		cal.set(Calendar.SECOND,      cal.getMaximum(Calendar.SECOND));
		cal.set(Calendar.MILLISECOND, cal.getMaximum(Calendar.MILLISECOND));
        return cal.getTime();
	}

    //-----------------------------------------------------------------------
	/**
	 * Returns a Date set to the first possible millisecond of the day, just
	 * after midnight. If a null day is passed in, a new Date is created.
	 * midnight (00m 00h 00s)
	 */
	public static Date getStartOfDay(Date day)
	{
		return getStartOfDay(day, Calendar.getInstance());
	}
	/**
	 * Returns a Date set to the first possible millisecond of the day, just
	 * after midnight. If a null day is passed in, a new Date is created.
	 * midnight (00m 00h 00s)
	 */
	public static Date getStartOfDay(Date day, Calendar cal)
	{
		if (day == null) day = new Date();
		cal.setTime(day);
		cal.set(Calendar.HOUR_OF_DAY, cal.getMinimum(Calendar.HOUR_OF_DAY));
		cal.set(Calendar.MINUTE,      cal.getMinimum(Calendar.MINUTE));
		cal.set(Calendar.SECOND,      cal.getMinimum(Calendar.SECOND));
		cal.set(Calendar.MILLISECOND, cal.getMinimum(Calendar.MILLISECOND));
        return cal.getTime();
	}

    /**
     * Returns a Date set just to Noon, to the closest possible millisecond
     * of the day. If a null day is passed in, a new Date is created.
     * nnoon (00m 12h 00s)
     */
    public static Date getNoonOfDay(Date day, Calendar cal)
    {
        if (day == null) day = new Date();
        cal.setTime(day);
        cal.set(Calendar.HOUR_OF_DAY, 12);
        cal.set(Calendar.MINUTE,      cal.getMinimum(Calendar.MINUTE));
        cal.set(Calendar.SECOND,      cal.getMinimum(Calendar.SECOND));
        cal.set(Calendar.MILLISECOND, cal.getMinimum(Calendar.MILLISECOND));
        return cal.getTime();
    }
    
    //-----------------------------------------------------------------------
    public static Date parseFromFormats(String aValue)
    {
        if (StringUtils.isEmpty(aValue)) return null;

        // get DateUtil's formats
        SimpleDateFormat formats[] = DateUtil.getFormats();
        if (formats == null) return null;

        // iterate over the array and parse
        Date myDate = null;
        for (int i = 0; i <formats.length; i++)
        {
            try
            {
                myDate = DateUtil.parse(aValue, formats[i]);
                //if (myDate instanceof Date) 
                return myDate;
            }
            catch (Exception e)
            {
                // do nothing because we want to try the next
                // format if current one fails
            }
       }
       // haven't returned so couldn't parse
       return null;
    }

    //-----------------------------------------------------------------------
    public static java.sql.Timestamp parseTimestampFromFormats(String aValue)
    {
        if (StringUtils.isEmpty(aValue)) return null;

        // call the regular Date formatter
        Date myDate = DateUtil.parseFromFormats(aValue);
        if (myDate != null) return new java.sql.Timestamp(myDate.getTime());
        return null;
    }
    //-----------------------------------------------------------------------
    /**
     * Returns a java.sql.Timestamp equal to the current time
    **/
    public static java.sql.Timestamp now()
    {
        return new java.sql.Timestamp(new java.util.Date().getTime());
    }

    //-----------------------------------------------------------------------
    /**
     * Returns a string the represents the passed-in date parsed
     * according to the passed-in format.  Returns an empty string
     * if the date or the format is null.
    **/
    public static String format(Date aDate, SimpleDateFormat aFormat)
    {
        if (aDate == null || aFormat == null ) { return ""; }
        synchronized (aFormat) 
        {
            return aFormat.format(aDate);
        }
    }

    //-----------------------------------------------------------------------
    /**
     * Tries to take the passed-in String and format it as a date string in the
     * the passed-in format.
    **/
    public static String formatDateString(String aString, SimpleDateFormat aFormat)
    {
        if (StringUtils.isEmpty(aString) || aFormat == null)  return "";
        try
        {
            java.sql.Timestamp aDate = parseTimestampFromFormats(aString);
            if (aDate != null)
            {
                return DateUtil.format(aDate, aFormat);
            }
        }
        catch (Exception e)
        {
            // Could not parse aString.
        }
        return "";
    }

    //-----------------------------------------------------------------------
    /**
     * Returns a Date using the passed-in string and format.  Returns null if the string
     * is null or empty or if the format is null.  The string must match the format.
    **/
    public static Date parse(String aValue, SimpleDateFormat aFormat) throws ParseException
    {
        if (StringUtils.isEmpty(aValue) || aFormat == null)
        {
            return null;
        }

        return aFormat.parse(aValue);
    }

    //-----------------------------------------------------------------------
    /**
     * Returns true if endDate is after startDate or if startDate equals endDate
     * or if they are the same date.  Returns false if either value is null.
    **/
    public static boolean isValidDateRange(Date startDate, Date endDate)
    {
        return isValidDateRange(startDate, endDate, true);
    }

    //-----------------------------------------------------------------------
    /**
     * Returns true if endDate is after startDate or if startDate equals endDate.
     * Returns false if either value is null.  If equalOK, returns true if the
     * dates are equal.
    **/
    public static boolean isValidDateRange(Date startDate, Date endDate, boolean equalOK)
    {
        // false if either value is null
        if (startDate == null || endDate == null) { return false; }

        if (equalOK)
        {
            // true if they are equal
            if (startDate.equals(endDate)) { return true; }
        }

        // true if endDate after startDate
        if (endDate.after(startDate)) { return true; }

        return false;
    }

    //-----------------------------------------------------------------------
    // returns full timestamp format
    public static java.text.SimpleDateFormat defaultTimestampFormat()
    {
        return new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
    }

    //-----------------------------------------------------------------------
    // convenience method returns minimal date format
    public static java.text.SimpleDateFormat get8charDateFormat()
    {
        return DateUtil.mFormat8chars;
    }

    //-----------------------------------------------------------------------
    // convenience method returns minimal date format
    public static java.text.SimpleDateFormat defaultDateFormat()
    {
        return DateUtil.friendlyDateFormat(true);
    }

    //-----------------------------------------------------------------------
    // convenience method
    public static String defaultTimestamp(Date date)
    {
        return DateUtil.format(date, DateUtil.defaultTimestampFormat());
    }
    
    //-----------------------------------------------------------------------
    // convenience method
    public static String defaultDate(Date date)
    {
        return DateUtil.format(date, DateUtil.defaultDateFormat());
    }

    //-----------------------------------------------------------------------
    // convenience method returns long friendly timestamp format
    public static java.text.SimpleDateFormat friendlyTimestampFormat()
    {
        return new java.text.SimpleDateFormat("dd.MM.yyyy HH:mm:ss");
    }

    //-----------------------------------------------------------------------
    // convenience method returns long friendly formatted timestamp
    public static String friendlyTimestamp(Date date)
    {
        return DateUtil.format(date, DateUtil.friendlyTimestampFormat());
    }

    //-----------------------------------------------------------------------
    // convenience method returns long friendly formatted timestamp
    public static String format8chars(Date date)
    {
        return DateUtil.format(date, mFormat8chars);
    }

    //-----------------------------------------------------------------------
    // convenience method returns long friendly formatted timestamp
    public static String formatIso8601Day(Date date)
    {
        return DateUtil.format(date, mFormatIso8601Day);
    }

    //-----------------------------------------------------------------------
    public static String formatRfc822(Date date)
    {
        return DateUtil.format(date,mFormatRfc822);
    }

    //-----------------------------------------------------------------------
    // This is a hack, but it seems to work
    public static String formatIso8601(Date date)
    {
        if (date == null) return "";
        
        // Add a colon 2 chars before the end of the string
        // to make it a valid ISO-8601 date.
         
        String str = DateUtil.format(date,mFormatIso8601);
        StringBuffer sb = new StringBuffer();
        sb.append( str.substring(0,str.length()-2) );
        sb.append( ":" );
        sb.append( str.substring(str.length()-2) );
        return sb.toString();
    }

    //-----------------------------------------------------------------------
    // convenience method returns minimal date format
    public static java.text.SimpleDateFormat minimalDateFormat()
    {
        return DateUtil.friendlyDateFormat(true);
    }

    //-----------------------------------------------------------------------
    // convenience method using minimal date format
    public static String minimalDate(Date date)
    {
        return DateUtil.format(date, DateUtil.minimalDateFormat());
    }

    //-----------------------------------------------------------------------
    // convenience method that returns friendly data format
    // using full month, day, year digits.
    public static java.text.SimpleDateFormat fullDateFormat()
    {
        return DateUtil.friendlyDateFormat(false);
    }

    //-----------------------------------------------------------------------
    public static String fullDate(Date date)
    {
        return DateUtil.format(date, DateUtil.fullDateFormat());
    }

    //-----------------------------------------------------------------------
    /** Returns a "friendly" date format.
     *  @param mimimalFormat Should the date format allow single digits.
    **/
    public static java.text.SimpleDateFormat friendlyDateFormat(boolean minimalFormat)
    {
        if (minimalFormat)
        {
            return new java.text.SimpleDateFormat("d.M.yy");
        }

        return new java.text.SimpleDateFormat("dd.MM.yyyy");
    }

    //-----------------------------------------------------------------------
    /**
     * Format the date using the "friendly" date format.
     */
    public static String friendlyDate(Date date, boolean minimalFormat)
    {
        return DateUtil.format(date, DateUtil.friendlyDateFormat(minimalFormat));
    }

    //-----------------------------------------------------------------------
    // convenience method
    public static String friendlyDate(Date date)
    {
        return DateUtil.format(date, DateUtil.friendlyDateFormat(true));
    }
    
    public static Date parseIso8601(String value) throws Exception
    {
        return ISO8601DateParser.parse(value);
    }
}
\ No newline at end of file

Added: incubator/roller/branches/roller_1.x/src/org/roller/util/ISO8601DateParser.java
URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_1.x/src/org/roller/util/ISO8601DateParser.java?rev=327589&view=auto
==============================================================================
--- incubator/roller/branches/roller_1.x/src/org/roller/util/ISO8601DateParser.java (added)
+++ incubator/roller/branches/roller_1.x/src/org/roller/util/ISO8601DateParser.java Fri Oct 21 14:27:36 2005
@@ -0,0 +1,122 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.roller.util;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.TimeZone;
+
+/**
+ * ISO 8601 date parsing utility.  Designed for parsing the ISO subset used in
+ * Dublin Core, RSS 1.0, and Atom.
+ * 
+ * @author <a href="mailto:burton@apache.org">Kevin A. Burton (burtonator)</a>
+ * @version $Id: ISO8601DateParser.java,v 1.2 2005/06/03 20:25:29 snoopdave Exp $
+ */
+public class ISO8601DateParser {
+
+    // 2004-06-14T19:GMT20:30Z
+    // 2004-06-20T06:GMT22:01Z
+
+    private static SimpleDateFormat df
+        = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ssz" );
+
+    // http://www.cl.cam.ac.uk/~mgk25/iso-time.html
+    //    
+    // http://www.intertwingly.net/wiki/pie/DateTime
+    //
+    // http://www.w3.org/TR/NOTE-datetime
+    //
+    // Different standards may need different levels of granularity in the date and
+    // time, so this profile defines six levels. Standards that reference this
+    // profile should specify one or more of these granularities. If a given
+    // standard allows more than one granularity, it should specify the meaning of
+    // the dates and times with reduced precision, for example, the result of
+    // comparing two dates with different precisions.
+
+    // The formats are as follows. Exactly the components shown here must be
+    // present, with exactly this punctuation. Note that the "T" appears literally
+    // in the string, to indicate the beginning of the time element, as specified in
+    // ISO 8601.
+
+    //    Year:
+    //       YYYY (eg 1997)
+    //    Year and month:
+    //       YYYY-MM (eg 1997-07)
+    //    Complete date:
+    //       YYYY-MM-DD (eg 1997-07-16)
+    //    Complete date plus hours and minutes:
+    //       YYYY-MM-DDThh:mmTZD (eg 1997-07-16T19:20+01:00)
+    //    Complete date plus hours, minutes and seconds:
+    //       YYYY-MM-DDThh:mm:ssTZD (eg 1997-07-16T19:20:30+01:00)
+    //    Complete date plus hours, minutes, seconds and a decimal fraction of a
+    // second
+    //       YYYY-MM-DDThh:mm:ss.sTZD (eg 1997-07-16T19:20:30.45+01:00)
+
+    // where:
+
+    //      YYYY = four-digit year
+    //      MM   = two-digit month (01=January, etc.)
+    //      DD   = two-digit day of month (01 through 31)
+    //      hh   = two digits of hour (00 through 23) (am/pm NOT allowed)
+    //      mm   = two digits of minute (00 through 59)
+    //      ss   = two digits of second (00 through 59)
+    //      s    = one or more digits representing a decimal fraction of a second
+    //      TZD  = time zone designator (Z or +hh:mm or -hh:mm)
+    public static Date parse( String input ) throws java.text.ParseException {
+
+        //NOTE: SimpleDateFormat uses GMT[-+]hh:mm for the TZ which breaks
+        //things a bit.  Before we go on we have to repair this.
+
+        //this is zero time so we need to add that TZ indicator for 
+        if ( input.endsWith( "Z" ) ) {
+            input = input.substring( 0, input.length() - 1) + "GMT-00:00";
+        } else {
+            int inset = 6;
+        
+            String s0 = input.substring( 0, input.length() - inset );
+            String s1 = input.substring( input.length() - inset, input.length() );
+
+            input = s0 + "GMT" + s1;
+        }
+        
+        return df.parse( input );
+        
+    }
+
+    public static String toString( Date date ) {
+
+        TimeZone tz = TimeZone.getTimeZone( "UTC" );
+        
+        df.setTimeZone( tz );
+
+        String output = df.format( date );
+
+        int inset0 = 9;
+        int inset1 = 6;
+        
+        String s0 = output.substring( 0, output.length() - inset0 );
+        String s1 = output.substring( output.length() - inset1, output.length() );
+
+        String result = s0 + s1;
+
+        result = result.replaceAll( "UTC", "+00:00" );
+        
+        return result;
+        
+    }
+
+}
\ No newline at end of file

Added: incubator/roller/branches/roller_1.x/src/org/roller/util/LRUCache.java
URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_1.x/src/org/roller/util/LRUCache.java?rev=327589&view=auto
==============================================================================
--- incubator/roller/branches/roller_1.x/src/org/roller/util/LRUCache.java (added)
+++ incubator/roller/branches/roller_1.x/src/org/roller/util/LRUCache.java Fri Oct 21 14:27:36 2005
@@ -0,0 +1,21 @@
+/*
+ * Created on Jun 15, 2004
+ */
+package org.roller.util;
+
+import java.util.Map;
+
+// David Flanaghan: http://www.davidflanagan.com/blog/000014.html
+public class LRUCache extends java.util.LinkedHashMap 
+{
+    protected int maxsize;
+    public LRUCache(int maxsize) 
+    {
+        super(maxsize*4/3 + 1, 0.75f, true);
+        this.maxsize = maxsize;
+    }
+    protected boolean removeEldestEntry(Map.Entry eldest) { 
+        return size() > this.maxsize; 
+    }
+}
+    

Added: incubator/roller/branches/roller_1.x/src/org/roller/util/LRUCache2.java
URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_1.x/src/org/roller/util/LRUCache2.java?rev=327589&view=auto
==============================================================================
--- incubator/roller/branches/roller_1.x/src/org/roller/util/LRUCache2.java (added)
+++ incubator/roller/branches/roller_1.x/src/org/roller/util/LRUCache2.java Fri Oct 21 14:27:36 2005
@@ -0,0 +1,160 @@
+package org.roller.util;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+/**
+ * LRU cache with per-entry timeout logic.
+ * 
+ * @author Dave Johnson
+ */
+public class LRUCache2
+{
+    private long timeout;
+    private Map cache = null;
+    private Environment environment = null;
+
+    /**
+     * Create cache.
+     * 
+     * @param maxsize
+     *            Maximum number of entries in cache.
+     * @param timeout
+     *            Entry timeout in milli-seconds.
+     */
+    public LRUCache2(int maxsize, long timeout)
+    {
+        this.environment = new DefaultEnvironment();
+        this.timeout = timeout;
+        this.cache = new LRULinkedHashMap(maxsize);
+    }
+
+    /**
+     * Create cache that uses custom environment.
+     * 
+     * @param maxsize
+     *            Maximum number of entries in cache.
+     * @param timeout
+     *            Entry timeout in milli-seconds.
+     */
+    public LRUCache2(Environment environment, int maxsize, long timeout)
+    {
+        this.environment = environment;
+        this.timeout = timeout;
+        this.cache = new LRULinkedHashMap(maxsize);
+    }
+
+    public synchronized void put(Object key, Object value)
+    {
+        CacheEntry entry = new CacheEntry(value, environment
+                        .getCurrentTimeInMillis());
+        cache.put(key, entry);
+    }
+
+    public Object get(Object key)
+    {
+        Object value = null;
+        CacheEntry entry = null;
+        synchronized(this)
+        {
+            entry = (CacheEntry) cache.get(key);
+        }
+        if (entry != null)
+        {
+            if (environment.getCurrentTimeInMillis() - entry.getTimeCached() < timeout)
+            {
+                value = entry.getValue();
+            }
+            else
+            {
+                cache.remove(entry);
+            }
+        }
+        return value;
+    }
+
+    public synchronized void purge()
+    {
+        cache.clear();
+    }
+
+    public synchronized void purge(String[] patterns)
+    {
+        List purgeList = new ArrayList();
+        Iterator keys = cache.keySet().iterator();
+        while (keys.hasNext())
+        {
+            String key = (String) keys.next();
+            for (int i = 0; i < patterns.length; i++)
+            {
+                if (key.indexOf(patterns[i]) != -1)
+                {
+                    purgeList.add(key);
+                    break;
+                }
+            }
+        }
+        Iterator purgeIter = purgeList.iterator();
+        while (purgeIter.hasNext())
+        {
+            String key = (String) purgeIter.next();
+            cache.remove(key);
+        }
+    }
+
+    public int size()
+    {
+        return cache.size();
+    }
+    public interface Environment
+    {
+        public long getCurrentTimeInMillis();
+    }
+    public static class DefaultEnvironment implements Environment
+    {
+        public long getCurrentTimeInMillis()
+        {
+            return System.currentTimeMillis();
+        }
+    }
+    private static class CacheEntry
+    {
+        private Object value;
+        private long timeCached = -1;
+
+        public CacheEntry(Object value, long timeCached)
+        {
+            this.timeCached = timeCached;
+            this.value = value;
+        }
+
+        public long getTimeCached()
+        {
+            return timeCached;
+        }
+
+        public Object getValue()
+        {
+            return value;
+        }
+    }
+    
+    // David Flanaghan: http://www.davidflanagan.com/blog/000014.html
+    private static class LRULinkedHashMap extends LinkedHashMap
+    {
+        protected int maxsize;
+
+        public LRULinkedHashMap(int maxsize)
+        {
+            super(maxsize * 4 / 3 + 1, 0.75f, true);
+            this.maxsize = maxsize;
+        }
+
+        protected boolean removeEldestEntry(Map.Entry eldest)
+        {
+            return this.size() > this.maxsize;
+        }
+    }
+}

Added: incubator/roller/branches/roller_1.x/src/org/roller/util/LinkbackExtractor.java
URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_1.x/src/org/roller/util/LinkbackExtractor.java?rev=327589&view=auto
==============================================================================
--- incubator/roller/branches/roller_1.x/src/org/roller/util/LinkbackExtractor.java (added)
+++ incubator/roller/branches/roller_1.x/src/org/roller/util/LinkbackExtractor.java Fri Oct 21 14:27:36 2005
@@ -0,0 +1,408 @@
+package org.roller.util;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.StringReader;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.swing.text.MutableAttributeSet;
+import javax.swing.text.html.HTML;
+import javax.swing.text.html.HTMLEditorKit;
+import javax.swing.text.html.HTML.Tag;
+import javax.swing.text.html.HTMLEditorKit.Parser;
+import javax.swing.text.html.HTMLEditorKit.ParserCallback;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import com.sun.syndication.feed.synd.SyndEntry;
+import com.sun.syndication.feed.synd.SyndFeed;
+import com.sun.syndication.io.FeedException;
+import com.sun.syndication.io.SyndFeedInput;
+
+/**
+ * Parses HTML file for referring linkback title and excerpt.
+ * 
+ * @author David M Johnson
+ */
+public class LinkbackExtractor
+{
+    private static Log mLogger        = LogFactory.getFactory().getInstance(
+                                              LinkbackExtractor.class);
+    private boolean    mFound         = false;
+    private String     mTitle         = "";
+    private String     mRssLink       = null;
+    private String     mExcerpt       = null;
+    private String     mPermalink     = null;
+    private int        mStart         = 0;
+    private int        mEnd           = 0;
+    private int        mMaxExcerpt    = 500;                           // characters
+    private String     mRequestURL    = null;
+    private String     mRequestURLWWW = null;
+    private String     mRefererURL;
+
+    //------------------------------------------------------------------------
+    /**
+     * Extract referring page title, excerpt, and permalink.
+     * 
+     * @param refererUrl
+     * @param requestUrl
+     */
+    public LinkbackExtractor(String refererURL, String requestURL)
+            throws MalformedURLException, IOException
+    {
+        try
+        {
+            extractByParsingHtml(refererURL, requestURL);
+            if (mRssLink != null)
+            {
+                extractByParsingRss(mRssLink, requestURL);
+            }
+        }
+        catch (Exception e)
+        {
+            if (mLogger.isDebugEnabled())
+            {
+                mLogger.debug("Extracting linkback", e);
+            }
+        }
+    }
+
+    //------------------------------------------------------------------------
+    private void extractByParsingHtml(String refererURL, String requestURL)
+            throws MalformedURLException, IOException
+    {
+        URL url = new URL(refererURL);
+        InputStream is = url.openStream();
+
+        mRefererURL = refererURL;
+
+        if (requestURL.startsWith("http://www."))
+        {
+            mRequestURLWWW = requestURL;
+            mRequestURL = "http://" + mRequestURLWWW.substring(11);
+        }
+        else
+        {
+            mRequestURL = requestURL;
+            mRequestURLWWW = "http://www." + mRequestURL.substring(7);
+        }
+
+        // Trick gets Swing's HTML parser
+        Parser parser = (new HTMLEditorKit() {
+            public Parser getParser()
+            {
+                return super.getParser();
+            }
+        }).getParser();
+
+        // Read HTML file into string
+        StringBuffer sb = new StringBuffer();
+        InputStreamReader isr = new InputStreamReader(is);
+        BufferedReader br = new BufferedReader(isr);
+        try
+        {
+            String line = null;
+            while ((line = br.readLine()) != null)
+            {
+                sb.append(line);
+            }
+        }
+        finally
+        {
+            br.close();
+        }
+
+        // Parse HTML string to find title and start and end position
+        // of the referring excerpt.
+        StringReader sr = new StringReader(sb.toString());
+        parser.parse(sr, new LinkbackCallback(), true);
+
+        if (mStart != 0 && mEnd != 0 && mEnd > mStart)
+        {
+            mExcerpt = sb.toString().substring(mStart, mEnd);
+            mExcerpt = Utilities.removeHTML(mExcerpt);
+
+            if (mExcerpt.length() > mMaxExcerpt)
+            {
+                mExcerpt = mExcerpt.substring(0, mMaxExcerpt) + "...";
+            }
+        }
+
+        if (mTitle.startsWith(">") && mTitle.length() > 1)
+        {
+            mTitle = mTitle.substring(1);
+        }
+    }
+
+    //------------------------------------------------------------------------
+    private void extractByParsingRss(String rssLink, String requestURL)
+            throws IllegalArgumentException, MalformedURLException, FeedException, IOException
+    {
+        SyndFeedInput feedInput = new SyndFeedInput();       
+        SyndFeed feed = feedInput.build(
+            new InputStreamReader(new URL(rssLink).openStream()));
+        Iterator itemIter = feed.getEntries().iterator();
+        String feedTitle = feed.getTitle();
+
+        int count = 0;
+
+        if (mLogger.isDebugEnabled())
+        {
+            mLogger.debug("Feed parsed, title: " + feedTitle);
+        }
+
+        while (itemIter.hasNext())
+        {
+            count++;
+            SyndEntry item = (SyndEntry) itemIter.next();
+            if (item.getDescription().getValue().indexOf(requestURL) != -1)
+            {
+                mFound = true;
+                mPermalink = item.getLink().toString();
+                if (feedTitle != null && feedTitle.trim().length() > 0)
+                {
+                    mTitle = feedTitle + ": " + item.getTitle();
+                }
+                else
+                {
+                    mTitle = item.getTitle();
+                }
+                mExcerpt = item.getDescription().getValue();
+                mExcerpt = Utilities.removeHTML(mExcerpt);
+                if (mExcerpt.length() > mMaxExcerpt)
+                {
+                    mExcerpt = mExcerpt.substring(0, mMaxExcerpt) + "...";
+                }
+                break;
+            }
+        }
+
+        if (mLogger.isDebugEnabled())
+        {
+            mLogger.debug("Parsed " + count + " articles, found linkback="
+                    + mFound);
+        }
+    }
+
+    //------------------------------------------------------------------------
+    /**
+     * Returns the excerpt.
+     * 
+     * @return String
+     */
+    public String getExcerpt()
+    {
+        return mExcerpt;
+    }
+
+    //------------------------------------------------------------------------
+    /**
+     * Returns the title.
+     * 
+     * @return String
+     */
+    public String getTitle()
+    {
+        return mTitle;
+    }
+
+    //------------------------------------------------------------------------
+    /**
+     * Returns the permalink.
+     * 
+     * @return String
+     */
+    public String getPermalink()
+    {
+        return mPermalink;
+    }
+
+    //------------------------------------------------------------------------
+    /**
+     * Sets the permalink.
+     * 
+     * @param permalink
+     *            The permalink to set
+     */
+    public void setPermalink(String permalink)
+    {
+        mPermalink = permalink;
+    }
+
+    /////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Parser callback that finds title and excerpt. As we walk through the HTML
+     * tags, we keep track of the most recently encountered divider tag in the
+     * mStart field. Once we find the referring permalink, we set the mFound
+     * flag. After that, we look for the next divider tag and save it's position
+     * in the mEnd field.
+     */
+    private final class LinkbackCallback extends ParserCallback
+    {
+        // Dividers
+        private Tag[] mDivTags    = { Tag.TD, Tag.DIV, Tag.SPAN,
+                                          Tag.BLOCKQUOTE, Tag.P, Tag.LI,
+                                          Tag.BR, Tag.HR, Tag.PRE, Tag.H1,
+                                          Tag.H2, Tag.H3, Tag.H4, Tag.H5,
+                                          Tag.H6 };
+
+        private List  mList       = Arrays.asList(mDivTags);
+
+        private Tag   mCurrentTag = null;
+
+        /**
+         * Look for divider tags and for the permalink.
+         * 
+         * @param tag
+         *            HTML tag
+         * @param atts
+         *            Attributes of that tag
+         * @param pos
+         *            Tag's position in file
+         */
+        public void handleStartTag(Tag tag, MutableAttributeSet atts, int pos)
+        {
+            if (mList.contains(tag) && !mFound)
+            {
+                mStart = pos;
+            }
+            else if (mList.contains(tag) && mFound && mEnd == 0)
+            {
+                mEnd = pos;
+            }
+            else if (tag.equals(Tag.A))
+            {
+                String href = (String) atts.getAttribute(HTML.Attribute.HREF);
+                if (href == null)
+                    return;
+                int hashPos = href.lastIndexOf('#');
+                if (hashPos != -1)
+                {
+                    href = href.substring(0, hashPos);
+                }
+                if (href != null
+                        && (href.equals(mRequestURL) || href
+                                .equals(mRequestURLWWW)))
+                {
+                    mFound = true;
+                }
+                else
+                {
+                    /*
+                     * if (mLogger.isDebugEnabled()) { mLogger.debug("No match:
+                     * "+href); }
+                     */
+                }
+            }
+            mCurrentTag = tag;
+        }
+
+        /**
+         * Needed to handle SPAN tag.
+         */
+        public void handleSimpleTag(Tag tag, MutableAttributeSet atts, int pos)
+        {
+            if (mList.contains(tag) && mFound && mEnd == 0)
+            {
+                mEnd = pos;
+            }
+            else if (tag.equals(Tag.LINK))
+            {
+                // Look out for RSS autodiscovery link
+                String title = (String) atts.getAttribute(HTML.Attribute.TITLE);
+                String type = (String) atts.getAttribute(HTML.Attribute.TYPE);
+                if (title != null && type != null
+                        && type.equals("application/rss+xml")
+                        && title.equals("RSS"))
+                {
+                    mRssLink = (String) atts.getAttribute(HTML.Attribute.HREF);
+
+                    if (mLogger.isDebugEnabled())
+                    {
+                        mLogger.debug("Found RSS link " + mRssLink);
+                    }
+
+                    if (mRssLink.startsWith("/") && mRssLink.length() > 1)
+                    {
+                        try
+                        {
+                            URL url = new URL(mRefererURL);
+                            mRssLink = url.getProtocol() + "://"
+                                    + url.getHost() + ":" + url.getPort()
+                                    + mRssLink;
+                        }
+                        catch (MalformedURLException e)
+                        {
+                            mRssLink = null;
+                            if (mLogger.isDebugEnabled())
+                            {
+                                mLogger.debug("Determining RSS URL", e);
+                            }
+                        }
+                    }
+                    else if (!mRssLink.startsWith("http"))
+                    {
+                        int slash = mRefererURL.lastIndexOf("/");
+                        if (slash != -1)
+                        {
+                            mRssLink = mRefererURL.substring(0, slash) + "/"
+                                    + mRssLink;
+                        }
+                    }
+                    if (mLogger.isDebugEnabled())
+                    {
+                        mLogger.debug("Qualified RSS link is " + mRssLink);
+                    }
+                }
+            }
+        }
+
+        /**
+         * Stop at the very first divider tag after the permalink.
+         * 
+         * @param tag
+         *            End tag
+         * @param pos
+         *            Position in HTML file
+         */
+        public void handleEndTag(Tag tag, int pos)
+        {
+            if (mList.contains(tag) && mFound && mEnd == 0)
+            {
+                mEnd = pos;
+            }
+            else if (mList.contains(tag) && !mFound)
+            {
+                mStart = pos;
+            }
+            else
+            {
+                mCurrentTag = null;
+            }
+        }
+
+        /**
+         * Get the page title
+         */
+        public void handleText(char[] data, int pos)
+        {
+            if (mCurrentTag != null && mCurrentTag.equals(Tag.TITLE))
+            {
+                String newText = new String(data);
+                if (mTitle.length() < 50)
+                {
+                    mTitle += newText;
+                }
+            }
+        }
+    }
+}
+

Added: incubator/roller/branches/roller_1.x/src/org/roller/util/LocaleComparator.java
URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_1.x/src/org/roller/util/LocaleComparator.java?rev=327589&view=auto
==============================================================================
--- incubator/roller/branches/roller_1.x/src/org/roller/util/LocaleComparator.java (added)
+++ incubator/roller/branches/roller_1.x/src/org/roller/util/LocaleComparator.java Fri Oct 21 14:27:36 2005
@@ -0,0 +1,3 @@
+package org.roller.util;

import java.util.Locale;
import java.util.Comparator;
import java.io.Serializable;

public class LocaleComparator implements Comparator, Serializable
{
    public int compare(Object obj1, Object obj2)
    {
        if (obj1 instanceof Locale && obj2 instanceof Locale)
        {
            Locale locale1 = (Locale)obj1;
            Locale locale2 = (Locale)obj2;
            int compName = locale1.getDisplayName().compareTo(locale2.getDisplayName());
            if (compName == 0)
            {
                return locale1.toString().compareTo(locale2.toString());
            }
            return compName;
        }
+        return 0;
    }
/* Do Comparators need to implement equals()? -Lance
    public boolean equals(Object obj)
    {
        if (obj instanceof LocaleComparator)
        {
            if (obj.equals(this)) return true;
        }
        return false;
    }
*/
+}
\ No newline at end of file

Added: incubator/roller/branches/roller_1.x/src/org/roller/util/MD5Encoder.java
URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_1.x/src/org/roller/util/MD5Encoder.java?rev=327589&view=auto
==============================================================================
--- incubator/roller/branches/roller_1.x/src/org/roller/util/MD5Encoder.java (added)
+++ incubator/roller/branches/roller_1.x/src/org/roller/util/MD5Encoder.java Fri Oct 21 14:27:36 2005
@@ -0,0 +1,72 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.roller.util;
+
+
+/**
+ * Encode an MD5 digest into a String.
+ * <p>
+ * The 128 bit MD5 hash is converted into a 32 character long String.
+ * Each character of the String is the hexadecimal representation of 4 bits
+ * of the digest.
+ *
+ * @author Remy Maucherat
+ * @version $Revision: 1.1 $ $Date: 2005/06/01 19:51:22 $
+ */
+
+public final class MD5Encoder {
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    private static final char[] hexadecimal =
+    {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+     'a', 'b', 'c', 'd', 'e', 'f'};
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Encodes the 128 bit (16 bytes) MD5 into a 32 character String.
+     *
+     * @param binaryData Array containing the digest
+     * @return Encoded MD5, or null if encoding failed
+     */
+    public String encode( byte[] binaryData ) {
+
+        if (binaryData.length != 16)
+            return null;
+
+        char[] buffer = new char[32];
+
+        for (int i=0; i<16; i++) {
+            int low = (int) (binaryData[i] & 0x0f);
+            int high = (int) ((binaryData[i] & 0xf0) >> 4);
+            buffer[i*2] = hexadecimal[high];
+            buffer[i*2 + 1] = hexadecimal[low];
+        }
+
+        return new String(buffer);
+
+    }
+
+
+}
+