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/06/09 05:19:20 UTC

svn commit: r189695 [18/67] - in /incubator/roller/trunk: ./ 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/src/org/roller/presentation/velocity/ contrib/plugins/src/org/roller/presentation/velocity/plugins/ contrib/plugins/src/org/roller/presentation/velocity/plugins/acronyms/ contrib/plugins/src/org/roller/presentation/velocity/plugins/bookmarks/ contrib/plugins/src/org/roller/presentation/velocity/plugins/email/ contrib/plugins/src/org/roller/presentation/velocity/plugins/jspwiki/ contrib/plugins/src/org/roller/presentation/velocity/plugins/radeox/ contrib/plugins/src/org/roller/presentation/velocity/plugins/readmore/ contrib/plugins/src/org/roller/presentation/velocity/plugins/smileys/ contrib/plugins/src/org/roller/presentation/velocity/plugins/textile/ contrib/plugins/src/org/roller/presentation/velocity/plugins/topictag/ custom/ custom/src/ custom/web/ docs/ docs/images/ docs/installguide/ docs/installguide/old/ docs/userguide/ docs/userguide/images/ docs/userguide/old/ metadata/ metadata/database/ metadata/database/hibernate/ metadata/xdoclet/ nbproject/ personal/ personal/eclipse/ personal/testing/ sandbox/ sandbox/planetroller/ sandbox/planetroller/metadata/ sandbox/planetroller/metadata/database/ sandbox/planetroller/src/ sandbox/planetroller/src/org/ sandbox/planetroller/src/org/roller/ sandbox/planetroller/src/org/roller/tools/ sandbox/planetroller/src/org/roller/tools/planet/ sandbox/planetroller/templates/ sandbox/planetroller/test/ sandbox/planetroller/test/org/ sandbox/planetroller/test/org/roller/ sandbox/planetroller/test/org/roller/model/ sandbox/planetroller/test/org/roller/tools/ sandbox/planetroller/test/org/roller/tools/planet/ sandbox/planetroller/testdata/ sandbox/planetroller/testdata/cache/ sandbox/planetroller/testdata/output/ sandbox/standalone/ sandbox/standalone/jspwiki/ sandbox/standalone/jspwiki/default/ sandbox/standalone/jspwiki/default/images/ sandbox/standalone/lib/ sandbox/standalone/src/ sandbox/standalone/src/org/ sandbox/standalone/src/org/roller/ sandbox/standalone/src/org/roller/jspwiki/ sandbox/standalone/src/org/roller/tomcat/ sandbox/standalone/src/org/roller/util/ sandbox/standalone/tests/ sandbox/standalone/tests/org/ sandbox/standalone/tests/org/roller/ sandbox/standalone/tests/org/roller/util/ sandbox/standalone/tomcat/ src/ src/org/ src/org/roller/ src/org/roller/business/ src/org/roller/business/hibernate/ src/org/roller/business/search/ src/org/roller/business/search/operations/ src/org/roller/business/utils/ src/org/roller/config/ src/org/roller/config/runtime/ src/org/roller/model/ src/org/roller/pojos/ src/org/roller/presentation/ src/org/roller/presentation/atomapi/ src/org/roller/presentation/bookmarks/ src/org/roller/presentation/bookmarks/actions/ src/org/roller/presentation/bookmarks/formbeans/ src/org/roller/presentation/bookmarks/tags/ src/org/roller/presentation/filters/ src/org/roller/presentation/forms/ src/org/roller/presentation/newsfeeds/ src/org/roller/presentation/pagecache/ src/org/roller/presentation/pagecache/rollercache/ src/org/roller/presentation/pings/ src/org/roller/presentation/planet/ src/org/roller/presentation/tags/ src/org/roller/presentation/tags/calendar/ src/org/roller/presentation/tags/menu/ src/org/roller/presentation/util/ src/org/roller/presentation/velocity/ src/org/roller/presentation/weblog/ src/org/roller/presentation/weblog/actions/ src/org/roller/presentation/weblog/formbeans/ src/org/roller/presentation/weblog/tags/ src/org/roller/presentation/website/ src/org/roller/presentation/website/actions/ src/org/roller/presentation/website/formbeans/ src/org/roller/presentation/website/tags/ src/org/roller/presentation/xmlrpc/ src/org/roller/util/ src/org/roller/util/rome/ tests/ tests/org/ tests/org/roller/ tests/org/roller/ant/ tests/org/roller/business/ tests/org/roller/presentation/ tests/org/roller/presentation/atomapi/ tests/org/roller/presentation/bookmarks/ tests/org/roller/presentation/filters/ tests/org/roller/presentation/velocity/ tests/org/roller/presentation/velocity/plugins/ tests/org/roller/presentation/velocity/plugins/smileys/ tests/org/roller/presentation/velocity/plugins/textile/ tests/org/roller/presentation/weblog/ tests/org/roller/presentation/xmlrpc/ tests/org/roller/util/ tests/org/roller/util/rome/ tools/ tools/buildtime/ tools/buildtime/ant-1.6.2/ tools/buildtime/findbugs/ tools/buildtime/findbugs/lib/ tools/buildtime/findbugs/plugin/ tools/buildtime/mockrunner-0.3/ tools/buildtime/mockrunner-0.3/lib/ tools/buildtime/mockrunner-0.35/ tools/buildtime/mockrunner-0.35/lib/ tools/buildtime/tomcat-4.1.24/ tools/buildtime/xdoclet-1.2/ tools/buildtime/xdoclet-1.2/lib/ tools/hibernate-2.1/ tools/hibernate-2.1/lib/ tools/lib/ tools/standard-1.0.3/ tools/standard-1.0.3/lib/ tools/standard-1.0.3/tld/ tools/struts-1.2.4/ tools/struts-1.2.4/lib/ web/ web/WEB-INF/ web/WEB-INF/classes/ web/WEB-INF/classes/flavors/ web/WEB-INF/classes/themes/ web/bookmarks/ web/editor/ web/editor/images/ web/images/ web/images/editor/ web/images/midas/ web/images/preview/ web/images/smileys/ web/planet/ web/tags/ web/templates/ web/theme/ web/theme/images/ web/theme/lavender/ web/theme/scripts/ web/theme/scripts/classes/ web/themes/ web/themes/basic/ web/themes/berkley/ web/themes/berkley/images/ web/themes/brushedmetal/ web/themes/brushedmetal/images/ web/themes/cheb/ web/themes/cheb/images/ web/themes/cheb/scripts/ web/themes/clean/ web/themes/currency-i18n/ web/themes/currency-i18n/images/ web/themes/currency/ web/themes/currency/images/ web/themes/grey2/ web/themes/moonshine/ web/themes/movablemanila/ web/themes/movablemanila/images/ web/themes/pacifica/ web/themes/robot/ web/themes/rolling/ web/themes/rolling/images/ web/themes/sotto/ web/themes/sotto/images/ web/themes/sotto/styles/ web/themes/sunsets/ web/themes/sunsets/images/ web/themes/sunsets/scripts/ web/themes/sunsets/styles/ web/themes/werner/ web/themes/x2/ web/themes/x2/images/ web/themes/x2/scripts/ web/themes/x2/styles/ web/weblog/ web/website/

Added: incubator/roller/trunk/src/org/roller/business/IndexManagerImpl.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/business/IndexManagerImpl.java?rev=189695&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/business/IndexManagerImpl.java (added)
+++ incubator/roller/trunk/src/org/roller/business/IndexManagerImpl.java Wed Jun  8 20:18:46 2005
@@ -0,0 +1,387 @@
+/*
+ * Created on Jul 18, 2003
+ *
+ * Authored by: Mindaugas Idzelis  (min@idzelis.com)
+ */
+package org.roller.business;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.analysis.standard.StandardAnalyzer;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.FSDirectory;
+import org.apache.lucene.store.RAMDirectory;
+import org.roller.RollerException;
+import org.roller.business.search.operations.AddEntryOperation;
+import org.roller.business.search.operations.IndexOperation;
+import org.roller.business.search.operations.ReIndexEntryOperation;
+import org.roller.business.search.operations.RebuildUserIndexOperation;
+import org.roller.business.search.operations.RemoveEntryOperation;
+import org.roller.business.search.operations.RemoveUserIndexOperation;
+import org.roller.business.search.operations.WriteToIndexOperation;
+import org.roller.model.IndexManager;
+import org.roller.model.ThreadManager;
+import org.roller.pojos.UserData;
+import org.roller.pojos.WeblogEntryData;
+
+import EDU.oswego.cs.dl.util.concurrent.ReadWriteLock;
+import EDU.oswego.cs.dl.util.concurrent.WriterPreferenceReadWriteLock;
+
+/**
+ * @author aim4min
+ * @author mraible (formatting and indexDir configurable)
+ * 
+ * This is the lucene manager. This is the central entry point into the Lucene
+ * searching API. This should be retrieved from the roller context, and not
+ * instatiated manually.
+ *  
+ */
+public class IndexManagerImpl implements IndexManager
+{
+    //~ Static fields/initializers
+    // =============================================
+
+    private IndexReader reader;
+
+    static Log mLogger = LogFactory.getFactory().getInstance(
+            IndexManagerImpl.class);
+
+    //~ Instance fields
+    // ========================================================
+
+    private ThreadManager mThreadManager;
+
+    File indexConsistencyMarker;
+
+    private boolean useRAMIndex = false;
+
+    private RAMDirectory fRAMindex;
+
+    private String indexDir = null;
+
+    private boolean inconsistentAtStartup = false;
+
+    private ReadWriteLock rwl = new WriterPreferenceReadWriteLock();
+
+    //~ Constructors
+    // ===========================================================
+
+    /**
+     * Creates a new lucene index manager. This should only be created once.
+     * Creating the index manager more than once will definately result in
+     * errors. The prefered way of getting an index is through the
+     * RollerContext.
+     * 
+     * @param indexDir -
+     *            the path to the index directory
+     */
+    public IndexManagerImpl(String indexDir, ThreadManager manager)
+    {
+        this.mThreadManager = manager;
+
+        this.indexDir = indexDir;
+
+        String test = indexDir + File.separator + ".index-inconsistent";
+        indexConsistencyMarker = new File(test);
+
+        // 1. If inconsistency marker exists.
+        //     Delete index
+        // 2. if we're using RAM index
+        //     load ram index wrapper around index
+        //
+        if (indexConsistencyMarker.exists())
+        {
+            getFSDirectory(true);
+            inconsistentAtStartup = true;
+        }
+        else
+        {
+            try
+            {
+                File makeIndexDir = new File(indexDir);
+                if (!makeIndexDir.exists())
+                {
+                    makeIndexDir.mkdirs();
+                    inconsistentAtStartup = true;
+                }
+                indexConsistencyMarker.createNewFile();
+            }
+            catch (IOException e)
+            {
+                mLogger.error(e);
+            }
+        }
+
+        if (indexExists())
+        {
+            if (useRAMIndex)
+            {
+                Directory filesystem = getFSDirectory(false);
+
+                try
+                {
+                    fRAMindex = new RAMDirectory(filesystem);
+                }
+                catch (IOException e)
+                {
+                    mLogger.error("Error creating in-memory index", e);
+                }
+            }
+        }
+        else
+        {
+            if (useRAMIndex)
+            {
+                fRAMindex = new RAMDirectory();
+                createIndex(fRAMindex);
+            }
+            else
+            {
+                createIndex(getFSDirectory(true));
+            }
+        }
+
+    }
+
+    //~ Methods
+    // ================================================================
+    
+    public void rebuildUserIndex() throws RollerException
+    {
+        scheduleIndexOperation( 
+                new RebuildUserIndexOperation(this, null));
+    }
+    
+    public void removeUserIndex(UserData user) throws RollerException
+    {
+        scheduleIndexOperation(
+                new RemoveUserIndexOperation(this, user));
+    }
+    
+    public void addEntryIndexOperation(WeblogEntryData entry) throws RollerException
+    {
+        AddEntryOperation addEntry = new AddEntryOperation(this, entry);
+        scheduleIndexOperation(addEntry);
+    }
+    
+    public void addEntryReIndexOperation(WeblogEntryData entry) throws RollerException
+    {
+        ReIndexEntryOperation reindex = new ReIndexEntryOperation(this, entry);
+        scheduleIndexOperation(reindex);
+    }
+ 
+    public void removeEntryIndexOperation(WeblogEntryData entry) throws RollerException
+    {
+        RemoveEntryOperation removeOp = new RemoveEntryOperation(this, entry);
+        executeIndexOperationNow(removeOp); 
+    }
+    
+    public ReadWriteLock getReadWriteLock()
+    {
+        return rwl;
+    }
+
+    public boolean isInconsistentAtStartup()
+    {
+        return inconsistentAtStartup;
+    }
+
+    /**
+     * This is the analyzer that will be used to tokenize comment text.
+     * 
+     * @return Analyzer to be used in manipulating the database.
+     */
+    public static final Analyzer getAnalyzer()
+    {
+        return new StandardAnalyzer();
+    }
+
+    public void scheduleIndexOperation(final IndexOperation op)
+    {
+        try
+        {
+            mThreadManager.executeInBackground(op);
+        }
+        catch (InterruptedException e)
+        {
+            mLogger.error("Error executing operation", e);
+        }
+    }
+
+    /**
+     * @param search
+     */
+    public void executeIndexOperationNow(final IndexOperation op)
+    {
+        try
+        {
+            mThreadManager.executeInForeground(op);
+        }
+        catch (InterruptedException e)
+        {
+            mLogger.error("Error executing operation", e);
+        }
+    }
+
+    public synchronized void resetSharedReader() 
+    {
+        reader = null;
+    }
+    public synchronized IndexReader getSharedIndexReader()
+    {
+        if (reader == null)
+        {
+            try
+            {
+                reader = IndexReader.open(getIndexDirectory());
+            }
+            catch (IOException e)
+            {
+            }
+        }
+        return reader;
+    }
+
+    /**
+     * Get the directory that is used by the lucene index. This method will
+     * return null if there is no index at the directory location. If we are
+     * using a RAM index, the directory will be a ram directory.
+     * 
+     * @return Directory The directory containing the index, or null if error.
+     */
+    public Directory getIndexDirectory()
+    {
+        if (useRAMIndex)
+        {
+            return fRAMindex;
+        }
+        else
+        {
+            return getFSDirectory(false);
+        }
+    }
+
+    private boolean indexExists()
+    {
+        return IndexReader.indexExists(indexDir);
+    }
+
+    Directory getFSDirectory(boolean delete)
+    {
+        Directory directory = null;
+
+        try
+        {
+            directory = FSDirectory.getDirectory(indexDir, delete);
+        }
+        catch (IOException e)
+        {
+            mLogger.error("Problem accessing index directory", e);
+        }
+
+        return directory;
+    }
+
+    private void createIndex(Directory dir)
+    {
+        IndexWriter writer = null;
+
+        try
+        {
+            writer = new IndexWriter(dir, IndexManagerImpl.getAnalyzer(), true);
+        }
+        catch (IOException e)
+        {
+            mLogger.error("Error creating index", e);
+        }
+        finally
+        {
+            try
+            {
+                if (writer != null)
+                {
+                    writer.close();
+                }
+            }
+            catch (IOException e)
+            {
+            }
+        }
+    }
+
+    private IndexOperation getSaveIndexOperation()
+    {
+        return new WriteToIndexOperation(this) {
+            public void doRun()
+            {
+                Directory dir = getIndexDirectory();
+                Directory fsdir = getFSDirectory(true);
+
+                IndexWriter writer = null;
+
+                try
+                {
+                    writer = new IndexWriter(fsdir, IndexManagerImpl
+                            .getAnalyzer(), true);
+
+                    writer.addIndexes(new Directory[] { dir });
+                    indexConsistencyMarker.delete();
+                }
+                catch (IOException e)
+                {
+                    mLogger.error("Problem saving index to disk", e);
+
+                    // Delete the directory, since there was a problem saving
+                    // the RAM contents
+                    getFSDirectory(true);
+                }
+                finally
+                {
+                    try
+                    {
+                        if (writer != null)
+                            writer.close();
+                    }
+                    catch (IOException e1)
+                    {
+                        mLogger.warn("Unable to close IndexWriter.");
+                    }
+                }
+
+            }
+        };
+    }
+
+    public void release() 
+    {
+        // no-op
+    }
+    
+    public void shutdown()
+    {
+        if (useRAMIndex)
+        {
+            scheduleIndexOperation(getSaveIndexOperation());
+        }
+        else
+        {
+            indexConsistencyMarker.delete();
+        }
+
+        try
+        {
+            if (reader != null)
+                reader.close();
+        }
+        catch (IOException e)
+        {
+            // won't happen, since it was
+        }
+    }
+}
\ No newline at end of file

Added: incubator/roller/trunk/src/org/roller/business/PersistenceStrategy.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/business/PersistenceStrategy.java?rev=189695&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/business/PersistenceStrategy.java (added)
+++ incubator/roller/trunk/src/org/roller/business/PersistenceStrategy.java Wed Jun  8 20:18:46 2005
@@ -0,0 +1,76 @@
+/*
+ * Created on Aug 13, 2003
+ */
+package org.roller.business;
+
+import java.io.Serializable;
+
+import org.roller.RollerException;
+import org.roller.pojos.PersistentObject;
+import org.roller.pojos.UserData;
+
+/**
+ * Persistence strategy masks underlying persistence mechanism. 
+ * State is held in thread local storage (TLS). 
+ * When you call begin(), a PersistenceSession object is associated with your thread.
+ * When you call release(), TLS is cleared.
+ * 
+ * @author Lance Lavandowska
+ * @author Dave Johnson
+ */
+public interface PersistenceStrategy extends Serializable
+{
+    /** 
+     * Save a persistent object to storage. This method is only needed when
+     * a new object is to be added to storage. 
+     */
+    public PersistentObject store(PersistentObject data)
+        throws RollerException;
+       
+    /** 
+     * Load an persistent object from storage. Object returned is a 
+     * persistent instance, meaning that changes to it will be automatically
+     * saved to storage the next time that commit() is called.
+     */
+    public PersistentObject load(
+        String id, Class cls) throws RollerException;
+        
+    /** 
+     * Remove an object from storage.
+     */
+    public void remove(PersistentObject po)
+        throws RollerException;
+        
+    /** 
+     * Remove an object from storage.
+     */
+    public void remove(String id, Class cls)
+        throws RollerException;
+        
+    /** 
+     * Release existing resources and start new session and transaction.
+     */
+    public void begin(UserData user) throws RollerException;
+    
+    /**
+     * Associate user with thread.
+     */
+    public void setUser(UserData user) throws RollerException;
+    public UserData getUser() throws RollerException;
+
+    /** 
+     * Commit all changes made to persistent objects since last call to begin.
+     */
+	public void commit() throws RollerException;
+
+    /**
+     * Rollback all changes since last call to begin.
+     */
+	public void rollback() throws RollerException;
+    
+    /** 
+     * Release associated resources (database connection, session, etc.) and
+     * clear thread local storage.
+     */
+    public void release() throws RollerException;
+}

Added: incubator/roller/trunk/src/org/roller/business/PingQueueManagerImpl.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/business/PingQueueManagerImpl.java?rev=189695&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/business/PingQueueManagerImpl.java (added)
+++ incubator/roller/trunk/src/org/roller/business/PingQueueManagerImpl.java Wed Jun  8 20:18:46 2005
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2005
+ * Anil R. Gangolli. All rights reserved.
+ *
+ * Distributed with the Roller Weblogger Project under the terms of the Roller Software
+ * License
+ */
+
+package org.roller.business;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.roller.RollerException;
+import org.roller.model.PingQueueManager;
+import org.roller.pojos.PingQueueEntryData;
+
+public abstract class PingQueueManagerImpl implements PingQueueManager
+{
+    protected PersistenceStrategy persistenceStrategy;
+
+    private static Log logger = LogFactory.getLog(PingQueueManagerImpl.class);
+
+    public PingQueueManagerImpl(PersistenceStrategy persistenceStrategy)
+    {
+        this.persistenceStrategy = persistenceStrategy;
+    }
+
+    public void release()
+    {
+    }
+
+    public PingQueueEntryData retrieveQueueEntry(String id) throws RollerException
+    {
+        return (PingQueueEntryData) persistenceStrategy.load(id, PingQueueEntryData.class);
+    }
+
+    public void storeQueueEntry(PingQueueEntryData pingQueueEntry) throws RollerException
+    {
+        if (logger.isDebugEnabled()) logger.debug("Storing ping queue entry: " + pingQueueEntry);
+        persistenceStrategy.store(pingQueueEntry);
+    }
+
+    public void removeQueueEntry(String id) throws RollerException
+    {
+        if (logger.isDebugEnabled()) logger.debug("Removing ping queue entry with id: " + id);
+        persistenceStrategy.remove(id, PingQueueEntryData.class);
+    }
+
+    public void removeQueueEntry(PingQueueEntryData pingQueueEntry) throws RollerException
+    {
+        if (logger.isDebugEnabled()) logger.debug("Removing ping queue entry: " + pingQueueEntry);
+        persistenceStrategy.remove(pingQueueEntry);
+    }
+
+}

Added: incubator/roller/trunk/src/org/roller/business/PingTargetManagerImpl.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/business/PingTargetManagerImpl.java?rev=189695&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/business/PingTargetManagerImpl.java (added)
+++ incubator/roller/trunk/src/org/roller/business/PingTargetManagerImpl.java Wed Jun  8 20:18:46 2005
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 2005
+ * Anil R. Gangolli. All rights reserved.
+ *
+ * Distributed with the Roller Weblogger Project under the terms of the Roller Software
+ * License
+ */
+
+package org.roller.business;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.roller.RollerException;
+import org.roller.model.AutoPingManager;
+import org.roller.model.PingTargetManager;
+import org.roller.model.RollerFactory;
+import org.roller.pojos.PingTargetData;
+import org.roller.pojos.WebsiteData;
+
+import java.net.InetAddress;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.UnknownHostException;
+import java.util.Iterator;
+import java.util.List;
+
+public abstract class PingTargetManagerImpl implements PingTargetManager
+{
+    protected PersistenceStrategy persistenceStrategy;
+
+    private static Log mLogger =
+        LogFactory.getFactory().getInstance(PingTargetManagerImpl.class);
+
+    public PingTargetManagerImpl(PersistenceStrategy persistenceStrategy)
+    {
+        this.persistenceStrategy = persistenceStrategy;
+    }
+
+    public void release()
+    {
+    }
+
+    public PingTargetData createCommonPingTarget(String name, String pingUrl) throws RollerException
+    {
+        return new PingTargetData(null, name, pingUrl, null);
+    }
+
+    public PingTargetData createCustomPingTarget(String name, String pingUrl, WebsiteData website) throws RollerException
+    {
+        if (website == null) throw new RollerException(new IllegalArgumentException("website == null"));
+        return new PingTargetData(null, name, pingUrl, website);
+    }
+
+    public void storePingTarget(PingTargetData pingTarget) throws RollerException
+    {
+        persistenceStrategy.store(pingTarget);
+    }
+
+    public PingTargetData retrievePingTarget(String id) throws RollerException
+    {
+        return (PingTargetData) persistenceStrategy.load(id, PingTargetData.class);
+    }
+
+    public void removePingTarget(String id) throws RollerException
+    {
+        // The retrieval is necessary in order to do the necessary cleanup of references in pingTarget.remove().
+        PingTargetData pingTarget = retrievePingTarget(id);
+        pingTarget.remove();
+    }
+
+    public boolean isNameUnique(PingTargetData pingTarget) throws RollerException
+    {
+        String name = pingTarget.getName();
+        if (name == null || name.trim().length() == 0) return false;
+
+        String id = pingTarget.getId();
+
+        // Determine the set of "brother" targets (custom or common) among which this name should be unique.
+        List brotherTargets = null;
+        WebsiteData website = pingTarget.getWebsite();
+        if (website == null)
+        {
+            brotherTargets = getCommonPingTargets();
+        }
+        else
+        {
+            brotherTargets = getCustomPingTargets(website);
+        }
+
+        // Within that set of targets, fail if there is a target with the same name and that target doesn't
+        // have the same id.
+        for (Iterator i = brotherTargets.iterator(); i.hasNext();)
+        {
+            PingTargetData brother = (PingTargetData) i.next();
+            // Fail if it has the same name but not the same id.
+            if (brother.getName().equals(name) && (id == null || !brother.getId().equals(id)))
+            {
+                return false;
+            }
+        }
+        // No conflict found
+        return true;
+    }
+
+    public boolean isUrlWellFormed(PingTargetData pingTarget) throws RollerException
+    {
+        String url = pingTarget.getPingUrl();
+        if (url == null || url.trim().length() == 0) return false;
+        try
+        {
+            URL parsedUrl = new URL(url);
+            // OK.  If we get here, it parses ok.  Now just check that the protocol is http and there is a host portion.
+            boolean isHttp = parsedUrl.getProtocol().equals("http");
+            boolean hasHost = (parsedUrl.getHost() != null) && (parsedUrl.getHost().trim().length() > 0);
+            return isHttp && hasHost;
+        }
+        catch (MalformedURLException e)
+        {
+            return false;
+        }
+    }
+
+    public boolean isHostnameKnown(PingTargetData pingTarget) throws RollerException
+    {
+        String url = pingTarget.getPingUrl();
+        if (url == null || url.trim().length() == 0) return false;
+        try
+        {
+            URL parsedUrl = new URL(url);
+            String host = parsedUrl.getHost();
+            if (host == null || host.trim().length() == 0) return false;
+            InetAddress addr = InetAddress.getByName(host);
+            return true;
+        }
+        catch (MalformedURLException e)
+        {
+            return false;
+        }
+        catch (UnknownHostException e)
+        {
+            return false;
+        }
+    }
+}

Added: incubator/roller/trunk/src/org/roller/business/PlanetManagerImpl.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/business/PlanetManagerImpl.java?rev=189695&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/business/PlanetManagerImpl.java (added)
+++ incubator/roller/trunk/src/org/roller/business/PlanetManagerImpl.java Wed Jun  8 20:18:46 2005
@@ -0,0 +1,244 @@
+package org.roller.business;
+
+import java.net.URL;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.text.MessageFormat;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.roller.RollerException;
+import org.roller.model.PlanetManager;
+import org.roller.model.Roller;
+import org.roller.pojos.PlanetConfigData;
+import org.roller.pojos.PlanetEntryData;
+import org.roller.pojos.PlanetGroupData;
+import org.roller.pojos.PlanetSubscriptionData;
+import org.roller.util.rome.DiskFeedInfoCache;
+import org.roller.util.LRUCache2;
+
+import com.sun.syndication.feed.synd.SyndEntry;
+import com.sun.syndication.feed.synd.SyndFeed;
+import com.sun.syndication.fetcher.FeedFetcher;
+import com.sun.syndication.fetcher.impl.FeedFetcherCache;
+import com.sun.syndication.fetcher.impl.HttpURLFeedFetcher;
+import com.sun.syndication.fetcher.impl.SyndFeedInfo;
+
+/**
+ * Base class for PlanetManager implementations.
+ * @author Dave Johnson
+ */
+public abstract class PlanetManagerImpl implements PlanetManager
+{
+    protected Roller roller = null;
+    protected PersistenceStrategy strategy;
+    protected Date lastUpdated = new Date();
+    protected List aggregation = null;
+    protected Map lastUpdatedByGroup = new HashMap();
+    
+    // Cache up to 20 aggregations, each for up to 30 minutes
+    // TODO: make this aggregation cache configurable
+    protected LRUCache2 aggregationsByGroup = 
+            new LRUCache2(20, 30 * 60 * 1000);
+
+    private static Log logger =
+        LogFactory.getFactory().getInstance(PlanetManagerImpl.class);
+        
+    public PlanetManagerImpl()
+    {   
+    }
+
+    public PlanetManagerImpl(PersistenceStrategy strategy, Roller roller)
+    {
+        this.strategy = strategy;
+        this.roller = roller;    
+    }
+    
+    public void refreshEntries() throws RollerException
+    {
+        Date now = new Date();
+        long startTime = System.currentTimeMillis();
+        PlanetConfigData config = getConfiguration();
+        if (config == null || config.getCacheDir() == null)
+        {
+            logger.warn("Planet cache directory not set, aborting refresh");
+            return;
+        }
+        FeedFetcherCache feedInfoCache = 
+                new DiskFeedInfoCache(config.getCacheDir());
+               
+        if (config.getProxyHost()!=null && config.getProxyPort() > 0)
+        {
+            System.setProperty("proxySet", "true");
+            System.setProperty("http.proxyHost", config.getProxyHost());
+            System.setProperty("http.proxyPort", 
+                    Integer.toString(config.getProxyPort()));
+        }
+        /** a hack to set 15 sec timeouts for java.net.HttpURLConnection */
+        System.setProperty("sun.net.client.defaultConnectTimeout", "15000");
+        System.setProperty("sun.net.client.defaultReadTimeout", "15000");
+
+        FeedFetcher feedFetcher = new HttpURLFeedFetcher(feedInfoCache);
+        //FeedFetcher feedFetcher = new HttpClientFeedFetcher(feedInfoCache);
+        feedFetcher.setUsingDeltaEncoding(false);
+        feedFetcher.setUserAgent("Roller Planet 1.1-dev"); 
+        
+        // Loop through all subscriptions in the system
+        Iterator subs = getAllSubscriptions();
+        while (subs.hasNext())
+        {
+            long subStartTime = System.currentTimeMillis();           
+            
+            // Fetch latest entries for each subscription
+            Set newEntries = new TreeSet();
+            PlanetSubscriptionData sub = (PlanetSubscriptionData)subs.next();
+            SyndFeed feed = null;
+            URL feedUrl = null;
+            Date lastUpdated = now;
+            try
+            {
+                feedUrl = new URL(sub.getFeedUrl());
+                logger.debug("Get feed from cache "+sub.getFeedUrl());
+                feed = feedFetcher.retrieveFeed(feedUrl);
+                SyndFeedInfo feedInfo = feedInfoCache.getFeedInfo(feedUrl);
+                if (feedInfo.getLastModified() != null) 
+                {
+                    long lastUpdatedLong = 
+                        ((Long)feedInfo.getLastModified()).longValue();
+                    if (lastUpdatedLong != 0)
+                    {
+                        lastUpdated = new Date(lastUpdatedLong);  
+                    }
+                }
+                Thread.sleep(100); // be nice
+            }
+            catch (Exception e)
+            {
+                logger.warn("ERROR parsing " + sub.getFeedUrl() 
+                    + " : " + e.getClass().getName() + " : " + e.getMessage());
+                logger.debug(e);
+                continue;
+            }
+            if (lastUpdated!=null && sub.getLastUpdated()!=null)
+            {
+                Calendar feedCal = Calendar.getInstance();
+                feedCal.setTime(lastUpdated);
+                
+                Calendar subCal = Calendar.getInstance();
+                subCal.setTime(sub.getLastUpdated());
+                
+                if (!feedCal.after(subCal)) 
+                {
+                    if (logger.isDebugEnabled())
+                    {
+                        String msg = MessageFormat.format(
+                            "   Skipping ({0} / {1})",
+                            new Object[] {
+                               lastUpdated, sub.getLastUpdated()});
+                       logger.debug(msg);
+                    }
+                    continue;
+                }
+            }
+            if (feed.getPublishedDate() != null)
+            {
+                sub.setLastUpdated(feed.getPublishedDate());
+                saveSubscription(sub);
+            }
+            
+            // Kludge for Feeds without entry dates: most recent entry is given
+            // feed's last publish date (or yesterday if none exists) and earler
+            // entries are placed at once day intervals before that.
+            Calendar cal = Calendar.getInstance();
+            if (sub.getLastUpdated() != null)
+            {
+                cal.setTime(sub.getLastUpdated());
+            }
+            else
+            {
+                cal.setTime(new Date());
+                cal.add(Calendar.DATE, -1);
+            }
+
+            // Populate subscription object with new entries
+            int count = 0;
+            Iterator entries = feed.getEntries().iterator();
+            while (entries.hasNext())
+            {
+                try 
+                {
+                    SyndEntry romeEntry = (SyndEntry) entries.next();
+                    PlanetEntryData entry = 
+                            new PlanetEntryData(feed, romeEntry, sub);
+                    if (entry.getPublished() == null)
+                    {                    
+                        logger.debug(
+                         "No published date, assigning fake date for "+feedUrl);
+                        entry.setPublished(cal.getTime());
+                    }
+                    if (entry.getPermalink() == null)
+                    {
+                     logger.warn("No permalink, rejecting entry from "+feedUrl);
+                    }
+                    else 
+                    {
+                        saveEntry(entry);
+                        newEntries.add(entry);
+                    }
+                    cal.add(Calendar.DATE, -1);
+                    count++;
+                } 
+                catch (Exception e)
+                {
+                    logger.error("ERROR processing subscription entry", e);
+                }
+            }
+            logger.debug("   Entry count: " + count);
+            if (count > 0) 
+            {
+                Iterator entryIter = sub.getEntries().iterator();
+                while (entryIter.hasNext())
+                {
+                    deleteEntry((PlanetEntryData)entryIter.next());
+                }
+                sub.purgeEntries();
+                sub.addEntries(newEntries); 
+            }
+            long subEndTime = System.currentTimeMillis();  
+            logger.info("   " + count + " - " 
+                    + ((subEndTime-subStartTime)/1000.0) 
+                    + " seconds to process (" + count + ") entries of " 
+                    + sub.getFeedUrl());
+        }
+        // Clear the aggregation cache
+        clearCachedAggregations();
+        
+        long endTime = System.currentTimeMillis();
+        logger.info("--- DONE --- Refreshed entries in " 
+                + ((endTime-startTime)/1000.0) + " seconds");
+    }
+
+    public synchronized void clearCachedAggregations() 
+    {
+        aggregation = null;
+        aggregationsByGroup.purge();
+        lastUpdatedByGroup.clear();
+        lastUpdated = new Date();
+    }
+    
+    public Date getLastUpdated()
+    {
+        return lastUpdated;
+    }
+    
+    public Date getLastUpdated(PlanetGroupData group)
+    {
+        return (Date)lastUpdatedByGroup.get(group);
+    }
+}

Added: incubator/roller/trunk/src/org/roller/business/PropertiesManagerImpl.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/business/PropertiesManagerImpl.java?rev=189695&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/business/PropertiesManagerImpl.java (added)
+++ incubator/roller/trunk/src/org/roller/business/PropertiesManagerImpl.java Wed Jun  8 20:18:46 2005
@@ -0,0 +1,243 @@
+/*
+ * PropertiesManagerImpl.java
+ *
+ * Created on April 21, 2005, 10:43 AM
+ */
+
+package org.roller.business;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.roller.RollerException;
+import org.roller.model.PropertiesManager;
+import org.roller.model.Roller;
+import org.roller.model.RollerFactory;
+import org.roller.pojos.RollerConfigData;
+import org.roller.pojos.RollerPropertyData;
+
+import java.util.Iterator;
+import java.util.Map;
+import java.util.HashMap;
+import org.roller.config.RollerRuntimeConfig;
+import org.roller.config.runtime.ConfigDef;
+import org.roller.config.runtime.DisplayGroup;
+import org.roller.config.runtime.PropertyDef;
+import org.roller.config.runtime.RuntimeConfigDefs;
+
+/**
+ * Abstract PropertiesManager implementation.
+ *
+ * @author Allen Gilliland
+ */
+public abstract class PropertiesManagerImpl implements PropertiesManager
+{
+
+    protected PersistenceStrategy mStrategy;
+
+    private static Log mLogger =
+        LogFactory.getFactory().getInstance(PropertiesManagerImpl.class);
+
+
+    /**
+     * Creates a new instance of PropertiesManagerImpl
+     */
+    public PropertiesManagerImpl(PersistenceStrategy strategy)
+    {
+        this.mStrategy = strategy;
+        init();
+    }
+
+    private void init()
+    {
+        Map props = null;
+        try
+        {
+            props = this.getProperties();
+            
+            if(props.size() < 1) {
+                // empty props table ... try migrating, then load defaults
+                props = migrateOldRollerConfig(props);
+                props = initializeMissingProps(props);
+            } else {
+                // found existing props ... check for new props
+                props = initializeMissingProps(props);
+            }
+            
+            // save our changes
+            this.store(props);
+        }
+        catch (Exception e)
+        {
+            mLogger.fatal("Failed to initialize runtime configuration properties."+
+                    "Please check that the database has been upgraded!", e);
+            throw new RuntimeException(e);
+        }
+
+    }
+
+    /**
+     * Save a single property
+     */
+    public void store(RollerPropertyData property) throws RollerException
+    {
+        this.mStrategy.store(property);
+    }
+
+    /**
+     * Save all properties
+     */
+    public void store(Map properties) throws RollerException
+    {
+        // just go through the list and store each property
+        Iterator props = properties.values().iterator();
+        while (props.hasNext())
+        {
+            try
+            {
+                this.mStrategy.store((RollerPropertyData) props.next());
+            }
+            catch (RollerException re)
+            {
+                mLogger.error("Couldn't store Roller property", re);
+                throw re;
+            }
+        }
+    }
+
+    public void release()
+    {
+    }
+
+
+    /**
+     * Migrate data from the old roller config.
+     * This is called only if the existing runtime properties are empty.
+     */
+    private Map migrateOldRollerConfig(Map props)
+    {
+        // try to get the old config
+        Roller roller = RollerFactory.getRoller();
+        RollerConfigData rollerConfig = null;
+
+        try
+        {
+            rollerConfig = roller.getConfigManager().getRollerConfig();
+        }
+        catch (Exception e)
+        {
+            // We currently treat any exception obtaining the roller config
+            // as if we had not found it.
+            mLogger.error(e);
+        }
+
+        if (rollerConfig != null)
+        {
+            mLogger.info("Found old roller config ... doing migration to new runtime properties.");
+            // copy over data
+            props.put("site.name",
+                new RollerPropertyData("site.name", rollerConfig.getSiteName()));
+            props.put("site.description",
+                new RollerPropertyData("site.description", rollerConfig.getSiteDescription()));
+            props.put("site.adminemail",
+                new RollerPropertyData("site.adminemail", rollerConfig.getEmailAddress()));
+            props.put("site.absoluteurl",
+                new RollerPropertyData("site.absoluteurl", rollerConfig.getAbsoluteURL()));
+            props.put("site.linkbacks.enabled",
+                new RollerPropertyData("site.linkbacks.enabled", rollerConfig.getEnableLinkback().toString()));
+            props.put("users.registration.enabled",
+                new RollerPropertyData("users.registration.enabled", rollerConfig.getNewUserAllowed().toString()));
+            props.put("users.themes.path",
+                new RollerPropertyData("users.themes.path", rollerConfig.getUserThemes()));
+            props.put("users.editor.pages",
+                new RollerPropertyData("users.editor.pages", rollerConfig.getEditorPages()));
+            props.put("users.comments.enabled",
+                new RollerPropertyData("users.comments.enabled", "true"));
+            props.put("users.comments.autoformat",
+                new RollerPropertyData("users.comments.autoformat", rollerConfig.getAutoformatComments().toString()));
+            props.put("users.comments.escapehtml",
+                new RollerPropertyData("users.comments.escapehtml", rollerConfig.getEscapeCommentHtml().toString()));
+            props.put("users.comments.emailnotify",
+                new RollerPropertyData("users.comments.emailnotify", rollerConfig.getEmailComments().toString()));
+            props.put("uploads.enabled",
+                new RollerPropertyData("uploads.enabled", rollerConfig.getUploadEnabled().toString()));
+            props.put("uploads.types.allowed",
+                new RollerPropertyData("uploads.types.allowed", rollerConfig.getUploadAllow()));
+            props.put("uploads.types.forbid",
+                new RollerPropertyData("uploads.types.forbid", rollerConfig.getUploadForbid()));
+            props.put("uploads.file.maxsize",
+                new RollerPropertyData("uploads.file.maxsize", rollerConfig.getUploadMaxFileMB().toString()));
+            props.put("uploads.dir.maxsize",
+                new RollerPropertyData("uploads.dir.maxsize", rollerConfig.getUploadMaxDirMB().toString()));
+            /* no longer part of runtime config
+            props.put("aggregator.enabled",
+                new RollerPropertyData("aggregator.enabled", rollerConfig.getEnableAggregator().toString()));
+            props.put("aggregator.cache.enabled",
+                new RollerPropertyData("aggregator.cache.enabled", rollerConfig.getRssUseCache().toString()));
+            props.put("aggregator.cache.timeout",
+                new RollerPropertyData("aggregator.cache.timeout", rollerConfig.getRssCacheTime().toString()));
+            props.put("debug.memory.enabled",
+                new RollerPropertyData("debug.memory.enabled", rollerConfig.getMemDebug().toString()));
+            */
+            props.put("spam.referers.ignorewords",
+                new RollerPropertyData("spam.referers.ignorewords", rollerConfig.getRefererSpamWords()));
+        }
+        else
+        {
+            mLogger.info("Old roller config not found ... default values will be loaded");
+        }
+        
+        return props;
+    }
+
+    
+    /**
+     * This method compares the property definitions in the RuntimeConfigDefs
+     * file with the properties in the given Map and initializes any properties
+     * that were not found in the Map.
+     *
+     * If the Map of props is empty/null then we will initialize all properties.
+     **/
+    private Map initializeMissingProps(Map props) {
+        
+	if(props == null)
+		props = new HashMap();
+
+        // start by getting our runtimeConfigDefs
+        RuntimeConfigDefs runtimeConfigDefs = 
+                RollerRuntimeConfig.getRuntimeConfigDefs();
+        
+        // iterator through all the definitions and add properties
+        // that are not already in our props map
+        ConfigDef configDef = null;
+        DisplayGroup dGroup = null;
+        PropertyDef propDef = null;
+        Iterator defs = runtimeConfigDefs.getConfigDefs().iterator();
+        while(defs.hasNext()) {
+            configDef = (ConfigDef) defs.next();
+            
+            Iterator groups = configDef.getDisplayGroups().iterator();
+            while(groups.hasNext()) {
+                dGroup = (DisplayGroup) groups.next();
+                
+                Iterator propdefs = dGroup.getPropertyDefs().iterator();
+                while(propdefs.hasNext()) {
+                    propDef = (PropertyDef) propdefs.next();
+                    
+                    // do we already have this prop?  if not then add it
+                    if(!props.containsKey(propDef.getName())) {
+                        RollerPropertyData newprop =
+                                new RollerPropertyData(propDef.getName(), propDef.getDefaultValue());
+                        
+                        props.put(propDef.getName(), newprop);
+                                
+                        mLogger.info("Found uninitialized property "+propDef.getName()+
+                                " ... setting value to ["+propDef.getDefaultValue()+"]");
+                    }
+                }
+            }
+        }
+        
+        return props;
+    }
+    
+}

Added: incubator/roller/trunk/src/org/roller/business/PubTimeData.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/business/PubTimeData.java?rev=189695&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/business/PubTimeData.java (added)
+++ incubator/roller/trunk/src/org/roller/business/PubTimeData.java Wed Jun  8 20:18:46 2005
@@ -0,0 +1,122 @@
+package org.roller.business;
+
+/**
+ * Pub date bean.
+ * @author David M Johnson
+ *
+ * @castor:class name="PubTime" table="weblogentry" id="id" key-generator="UUID"
+ */
+public class PubTimeData
+   extends org.roller.pojos.PersistentObject
+   implements java.io.Serializable
+{
+   static final long serialVersionUID = -5669288520200229343L;
+
+   protected java.lang.String id;
+   protected java.sql.Timestamp pubTime;
+
+   public PubTimeData()
+   {
+   }
+
+   public PubTimeData( java.lang.String id,java.sql.Timestamp pubTime )
+   {
+      this.id = id;
+      this.pubTime = pubTime;
+   }
+
+   public PubTimeData( PubTimeData otherData )
+   {
+      this.id = otherData.id;
+      this.pubTime = otherData.pubTime;
+
+   }
+
+   /** @castor:field set-method="setId"
+     * @castor:field-sql name="id" sql-dirty="check" dirty="check"
+     * @castor:field-xml node="attribute"
+     */
+   public java.lang.String getId()
+   {
+      return this.id;
+   }
+   public void setId( java.lang.String id )
+   {
+      this.id = id;
+   }
+
+   /** Pub date.
+     * @castor:field set-method="setPubTime"
+     * @castor:field-sql name="pubtime" sql-dirty="check" dirty="check" 
+     */
+   public java.sql.Timestamp getPubTime()
+   {
+      return this.pubTime;
+   }
+   public void setPubTime( java.sql.Timestamp pubTime )
+   {
+      this.pubTime = pubTime;
+   }
+
+   public String toString()
+   {
+      StringBuffer str = new StringBuffer("{");
+
+      str.append("id=" + id + " " + "pubTime=" + pubTime);
+      str.append('}');
+
+      return(str.toString());
+   }
+
+   public boolean equals( Object pOther )
+   {
+      if( pOther instanceof PubTimeData )
+      {
+         PubTimeData lTest = (PubTimeData) pOther;
+         boolean lEquals = true;
+
+         if( this.id == null )
+         {
+            lEquals = lEquals && ( lTest.id == null );
+         }
+         else
+         {
+            lEquals = lEquals && this.id.equals( lTest.id );
+         }
+         if( this.pubTime == null )
+         {
+            lEquals = lEquals && ( lTest.pubTime == null );
+         }
+         else
+         {
+            lEquals = lEquals && this.pubTime.equals( lTest.pubTime );
+         }
+
+         return lEquals;
+      }
+      else
+      {
+         return false;
+      }
+   }
+
+   public int hashCode()
+   {
+      int result = 17;
+      result = 37*result + ((this.id != null) ? this.id.hashCode() : 0);
+      result = 37*result + ((this.pubTime != null) ? this.pubTime.hashCode() : 0);
+      return result;
+      }
+
+   /**
+	* Setter is needed in RollerImpl.storePersistentObject()
+    */
+   public void setData( org.roller.pojos.PersistentObject otherData )
+   {
+
+      this.id = ((PubTimeData)otherData).id;
+
+      this.pubTime = ((PubTimeData)otherData).pubTime;
+   }
+
+}

Added: incubator/roller/trunk/src/org/roller/business/RefererManagerImpl.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/business/RefererManagerImpl.java?rev=189695&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/business/RefererManagerImpl.java (added)
+++ incubator/roller/trunk/src/org/roller/business/RefererManagerImpl.java Wed Jun  8 20:18:46 2005
@@ -0,0 +1,552 @@
+package org.roller.business;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.roller.RollerException;
+import org.roller.config.RollerRuntimeConfig;
+import org.roller.model.ParsedRequest;
+import org.roller.model.RefererManager;
+import org.roller.model.Roller;
+import org.roller.model.RollerFactory;
+import org.roller.pojos.RefererData;
+import org.roller.pojos.WeblogEntryData;
+import org.roller.pojos.WebsiteData;
+import org.roller.util.DateUtil;
+import org.roller.util.LinkbackExtractor;
+import org.roller.util.Utilities;
+
+
+/**
+ * Abstract base implementation using PersistenceStrategy.
+ * @author Dave Johnson
+ * @author Lance Lavandowska
+ */
+public abstract class RefererManagerImpl implements RefererManager
+{
+    static Log mLogger =
+        LogFactory.getFactory().getInstance(RefererManagerImpl.class);
+
+    protected static final String DAYHITS = "dayHits";
+    protected static final String TOTALHITS = "totalHits";
+
+    protected PersistenceStrategy mStrategy;
+    protected Date mRefDate = new Date();
+    protected SimpleDateFormat mDateFormat = DateUtil.get8charDateFormat();
+
+    protected abstract List getReferersWithSameTitle(
+                    WebsiteData website, 
+                    String requestUrl, 
+                    String title, 
+                    String excerpt)
+                    throws RollerException;
+                    
+    protected abstract List getExistingReferers(
+                    WebsiteData website, 
+                    String dateString,
+                    String permalink) throws RollerException;
+
+    protected abstract List getReferersToWebsite(
+                    WebsiteData website, 
+                    String refererUrl) throws RollerException;
+
+    protected abstract List getMatchingReferers(
+                    WebsiteData website, 
+                    String requestUrl,
+                    String refererUrl) throws RollerException;
+
+    //-----------------------------------------------------------------------
+
+    public RefererManagerImpl()
+    {
+    }
+
+    //-----------------------------------------------------------------------
+
+    protected abstract int getHits(WebsiteData website, String type)
+        throws RollerException;
+
+    //------------------------------------------------------------------------
+
+    public void release()
+    {
+    }
+
+    //-----------------------------------------------------------------------
+    public synchronized void forceTurnover(String websiteId) throws RollerException
+    {
+        mLogger.debug("forceTurnover");
+        checkForTurnover(true, websiteId);
+    }
+
+    //--------------------------------------------------------- Get hit counts
+
+    public int getDayHits(WebsiteData website) throws RollerException
+    {
+        return getHits(website, DAYHITS);
+    }
+
+    //-----------------------------------------------------------------------
+
+    public int getTotalHits(WebsiteData website) throws RollerException
+    {
+        return getHits(website, TOTALHITS);
+    }
+
+
+    //------------------------------------------------------- Referer Storage
+
+    /**
+     * @see org.roller.pojos.RefererManager#removeReferer(java.lang.String)
+     */
+    public void removeReferer(String id) throws RollerException
+    {
+        mStrategy.remove(id, RefererData.class);
+    }
+
+    //-----------------------------------------------------------------------
+
+    /**
+     * @see org.roller.pojos.RefererManager#retrieveReferer(java.lang.String)
+     */
+    public RefererData retrieveReferer(String id) throws RollerException
+    {
+        return (RefererData)mStrategy.load(id,RefererData.class);
+    }
+
+    //-----------------------------------------------------------------------
+
+    /**
+     * @see org.roller.pojos.RefererManager#storeReferer(
+     * org.roller.pojos.RefererData)
+     */
+    public void storeReferer(RefererData data) throws RollerException
+    {
+        mStrategy.store(data);
+    }
+
+    //-----------------------------------------------------------------------
+    public List getEntryReferers(String entryId, boolean authorized)
+        throws RollerException
+    {
+        //TODO: Redesign this so this is performed using the DB query, and
+        // not in java code for perf/memory reasons
+        List authorizedvisible = new ArrayList();
+        List referers = getReferersToEntry(entryId);
+        for (Iterator rItr = referers.iterator(); rItr.hasNext();) 
+        {
+            RefererData referer = (RefererData) rItr.next();
+            if ( referer.getVisible().booleanValue() || authorized )
+            {
+                authorizedvisible.add( referer );
+            }
+        }
+
+        return authorizedvisible;
+    }
+
+    //------------------------------------------------------------------------
+
+    /**
+     * Process incoming request for referer information.
+     *
+     * <p>If there is no referer, treat it as a direct request.</p>
+     *
+     * <p>If there is a referer and there is no record for that referer, then
+     * parse the refering page for title and excerpt surround the refering link.
+     * If the excerpt cannot be found, then ignore the referer because it is
+     * fake - probably a referer spam.
+     * </p>
+     *
+     * @return boolean True if the referer header contains an ignore/spam word.
+     * @see org.roller.pojos.RefererManager#processRequest(ParsedRequest)
+     */
+    public boolean processRequest( ParsedRequest request )
+    {
+        String msg = "processRequest";
+        if ( request.getWebsite() == null ) return false;
+
+        try
+        {
+            List matchRef = null;
+
+            String requestUrl     = request.getRequestURL();
+            String refererUrl     = request.getRefererURL();
+            WebsiteData website   = request.getWebsite();
+            WeblogEntryData entry = request.getWeblogEntry();
+            String selfSiteFragment = "/page/" + website.getUser().getUserName();
+
+            String dateString = null;
+            if ( request.getDateString()!=null && request.isDateSpecified())
+            {
+                dateString = request.getDateString();
+            }
+
+            if (mLogger.isDebugEnabled())
+            {
+                mLogger.debug( msg+": refurl="+refererUrl );
+            }
+
+            /* Check Referer URL against selfSiteFragment (treat as direct),
+             * against a regex for an self-site editor page (direct),
+             * and against the Spam lists.
+             */
+            if ( refererUrl != null )
+            {                
+                // treat own URL as direct
+                if (refererUrl.indexOf(selfSiteFragment) != -1)
+                {
+                    refererUrl = null;
+                }
+                else                
+                {
+                    // treat editor referral as direct
+                    int lastSlash = requestUrl.indexOf("/", 8);
+                    if (lastSlash == -1) lastSlash = requestUrl.length();
+                    String requestSite = requestUrl.substring(0, lastSlash);
+                    if (refererUrl.matches(requestSite + ".*\\.do.*")) 
+                    {
+                        refererUrl = null;
+                    }
+                    else
+                    {
+                        // If referer URL contains spamWords or ignoreWords then don't log it.
+                        boolean isRefererSpam = checkForSpam(refererUrl, website);
+                        if (isRefererSpam) return true;
+                    }
+                }
+            }
+
+            // try to find existing RefererData for refererUrl
+            if (refererUrl == null || refererUrl.trim().length() < 8)
+            {
+                refererUrl = "direct";
+
+                // Get referer specified by referer URL of direct
+                matchRef = getReferersToWebsite(website, refererUrl);
+            }
+            else
+            {
+                refererUrl = Utilities.stripJsessionId(refererUrl);
+
+                // Query for referer with same referer and request URLs
+                matchRef = getMatchingReferers(website, requestUrl, refererUrl);
+
+                // If referer was not found, try adding or leaving off 'www'
+                if ( matchRef.size() == 0 )
+                {
+                    String secondTryUrl = null;
+                    if ( refererUrl.startsWith("http://www") )
+                    {
+                        secondTryUrl = "http://"+refererUrl.substring(11);
+                    }
+                    else
+                    {
+                        secondTryUrl = "http://www"+refererUrl.substring(7);
+                    }
+
+                    matchRef = getMatchingReferers(
+                        website, requestUrl, secondTryUrl);
+                    if ( matchRef.size() == 1 )
+                    {
+                        refererUrl = secondTryUrl;
+                    }
+                }
+            }
+
+            if (matchRef.size() == 1)
+            {
+                // Referer was found in database, so bump up hit count
+                RefererData ref = (RefererData)matchRef.get(0);
+
+                ref.setDayHits(
+                    new Integer(ref.getDayHits().intValue() + 1));
+                ref.setTotalHits(
+                    new Integer(ref.getTotalHits().intValue() + 1));
+
+                if (mLogger.isDebugEnabled())
+                {
+                    mLogger.debug(
+                        "Incrementing hit count on existing referer: "+refererUrl);
+                }
+
+                storeReferer(ref);
+                mStrategy.commit();
+            }
+            else if (matchRef.size() == 0)
+            {
+                // Referer was not found in database, so new Referer object
+                Integer one = new Integer(1);
+                RefererData ref =
+                    new RefererData(
+                        null,
+                        website,
+                        entry,
+                        dateString,
+                        refererUrl,
+                        null,
+                        requestUrl,
+                        null,
+                        null,
+                        Boolean.FALSE,
+                        Boolean.FALSE,
+                        one,
+                        one);
+
+                 if (mLogger.isDebugEnabled())
+                 {
+                    mLogger.debug("newReferer="+ref.getRefererUrl());
+                 }
+
+                 String refurl = ref.getRefererUrl();
+
+                 // If not a direct or search engine then search for linkback
+                 if (    request.isEnableLinkback()
+                      && request.isDateSpecified()
+                      && !refurl.equals("direct")
+                      && !refurl.startsWith("http://google")
+                      && !refurl.startsWith("http://www.google")
+                      && !refurl.startsWith("http://search.netscape")
+                      && !refurl.startsWith("http://www.blinkpro")
+                      && !refurl.startsWith("http://auto.search.msn")
+                      && !refurl.startsWith("http://search.yahoo")
+                      && !refurl.startsWith("http://uk.search.yahoo")
+                      && !refurl.startsWith("http://www.javablogs.com")
+                      && !refurl.startsWith("http://www.teoma")
+                    )
+                 {
+                     // Launch thread to extract referer linkback
+
+                    try
+                    {
+                        Roller mRoller = RollerFactory.getRoller();
+                       mRoller.getThreadManager().executeInBackground( 
+                          new LinkbackExtractorRunnable(ref) );
+                    } 
+                    catch (InterruptedException e) {
+                        mLogger.warn("Interrupted during linkback extraction",e);
+                    }
+                 }
+                 else
+                 {
+                     storeReferer(ref);
+                     mStrategy.commit();
+                 }
+            }
+        }
+        catch (RollerException pe)
+        {
+            mLogger.error(msg, pe);
+        }
+        catch (NullPointerException npe)
+        {
+            mLogger.error(msg, npe);
+        }
+        
+        return false;
+    }
+    
+    /**
+     * Check the Referer URL against the Site-wide RefererSpamWords list
+     * and against the user's own IgnoreWords list.  If the Referer contains
+     * any of the words from either list consider it Spam.
+     * 
+     * @param refererUrl
+     * @return
+     * @throws RollerException
+     */
+    private boolean checkForSpam(String refererUrl, WebsiteData website) throws RollerException
+    {
+        String spamwords = RollerRuntimeConfig.getProperty("spam.referers.ignorewords");
+        LinkedList spamWords = new LinkedList(Arrays.asList(
+                StringUtils.split(StringUtils.deleteWhitespace(spamwords), ",")));
+    
+        if ( website.getIgnoreWords() != null )
+        {
+            spamWords.addAll( 
+                Arrays.asList(StringUtils.split(
+                    StringUtils.deleteWhitespace(
+                        website.getIgnoreWords()),",")));
+        }
+        for( Iterator i = spamWords.iterator(); i.hasNext(); )
+        {
+            String word = (String)i.next();
+            if (refererUrl.indexOf(word) != -1)
+            {
+                if (mLogger.isDebugEnabled())
+                {
+                    mLogger.debug("Flagged a Spam because '" + word + 
+                                  "' was found in '" + refererUrl + "'");
+                }
+                refererUrl = null;
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Use LinkbackExtractor to parse title and excerpt from referer
+     */
+    class LinkbackExtractorRunnable implements Runnable
+    {
+
+        private RefererData mReferer = null;
+
+        public LinkbackExtractorRunnable( RefererData referer)
+        {
+            mReferer = referer;
+        }
+
+        public void run()
+        {
+
+            try
+            {
+                LinkbackExtractor lb = new LinkbackExtractor(
+                    mReferer.getRefererUrl(),mReferer.getRequestUrl());
+
+                if ( lb.getTitle()!=null && lb.getExcerpt()!=null )
+                {
+                    mReferer.setTitle(lb.getTitle());
+                    mReferer.setExcerpt(lb.getExcerpt());
+
+
+                    if ( lb.getPermalink() != null )
+                    {
+                        // The presence of a permalink indicates that this
+                        // linkback was parsed out of an RSS feed and is
+                        // presumed to be a good linkback.
+
+                        mReferer.setRefererPermalink(lb.getPermalink());
+
+                        // See if this request/permalink is in the DB
+                        List matchRef = getExistingReferers(
+                            mReferer.getWebsite(),
+                            mReferer.getDateString(),
+                            mReferer.getRefererPermalink());
+
+                        // If it is the first, then set it to be visible
+                        if ( matchRef.size() == 0 )
+                        {
+                            mReferer.setVisible(Boolean.TRUE);
+                        }
+                        else
+                        {
+                            // We can't throw away duplicates or we will
+                            // end up reparsing them everytime a hit comes
+                            // in from one of them, but we can mark them
+                            // as duplicates.
+                            mReferer.setDuplicate(Boolean.TRUE);
+                        }
+
+                        storeReferer(mReferer);
+                    }
+
+                    else
+                    {
+                        // Store the new referer
+                        storeReferer(mReferer);
+
+                        // Hacky Referer URL weighting kludge:
+                        //
+                        // If there are multple referers to a request URL,
+                        // then we want to pick the best one. The others
+                        // are marked as duplicates. To do this we use a
+                        // weight. The weight formula is:
+                        //
+                        // w = URL length + (100 if URL contains anchor)
+
+                        // LOOP: find the referer with the highest weight
+                        Boolean visible = Boolean.FALSE;
+                        List refs= getReferersWithSameTitle(
+                            mReferer.getWebsite(),
+                            mReferer.getRequestUrl(),
+                            lb.getTitle(),
+                            lb.getExcerpt());
+                        RefererData chosen = null;
+                        int maxweight = 0;
+                        for (Iterator rdItr = refs.iterator();rdItr.hasNext();)
+                        {
+                            RefererData referer = (RefererData) rdItr.next();
+
+                            int weight = referer.getRefererUrl().length();
+                            if (referer.getRefererUrl().indexOf('#') != -1)
+                            {
+                                weight += 100;
+                            }
+
+                            if ( weight > maxweight )
+                            {
+                                chosen = referer;
+                                maxweight = weight;
+                            }
+
+                            if (referer.getVisible().booleanValue())
+                            {
+                                // If any are visible then chosen
+                                // replacement must be visible as well.
+                                visible = Boolean.TRUE;
+                            }
+
+                        }
+
+                        // LOOP: to mark all of the lower weight ones
+                        // as duplicates
+                        for (Iterator rdItr = refs.iterator();rdItr.hasNext();) {
+                            RefererData referer = (RefererData) rdItr.next();
+
+                            if (referer != chosen)
+                            {
+                                referer.setDuplicate(Boolean.TRUE);
+                            }
+                            else
+                            {
+                                referer.setDuplicate(Boolean.FALSE);
+                                referer.setVisible(visible);
+                            }
+                            storeReferer(referer);
+                        }
+
+
+                    }
+                }
+                else
+                {
+                    // It is not a linkback, but store it anyway
+                    storeReferer(mReferer);
+
+                    mLogger.info("No excerpt found at refering URL "
+                        + mReferer.getRefererUrl());
+                }
+            }
+            catch (Exception e)
+            {
+                mLogger.error("Processing linkback",e);
+            }
+            finally
+            {
+                try {
+                    mStrategy.release();
+                }
+                catch (RollerException e) {
+                    mLogger.error(
+                    "Exception logged by ManagerSupport.releaseDatabase()");
+                }
+            }
+
+        }
+
+    }
+
+}
+
+

Added: incubator/roller/trunk/src/org/roller/business/RollerImpl.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/business/RollerImpl.java?rev=189695&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/business/RollerImpl.java (added)
+++ incubator/roller/trunk/src/org/roller/business/RollerImpl.java Wed Jun  8 20:18:46 2005
@@ -0,0 +1,110 @@
+/*
+ * RollerImpl.java
+ *
+ * Created on April 29, 2005, 5:33 PM
+ */
+
+package org.roller.business;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.roller.RollerException;
+import org.roller.config.RollerConfig;
+import org.roller.model.FileManager;
+import org.roller.model.IndexManager;
+import org.roller.model.Roller;
+import org.roller.model.ThreadManager;
+import org.roller.util.StringUtils;
+
+
+/**
+ * The abstract version of the Roller implementation.
+ * Here we put code that pertains to *all* implementations of the Roller
+ * interface, regardless of their persistence strategy.
+ *
+ * @author Allen Gilliland
+ */
+public abstract class RollerImpl implements Roller {
+    
+    private static Log mLogger = 
+        LogFactory.getFactory().getInstance(RollerImpl.class);
+    
+    protected FileManager     mFileManager = null;
+    protected IndexManager    mIndexManager = null;
+    protected ThreadManager   mThreadManager = null;
+    
+    
+    /** Creates a new instance of RollerImpl */
+    public RollerImpl() {
+        // nothing to do here yet
+    }
+    
+    
+    /** 
+     * @see org.roller.model.Roller#getFileManager()
+     */
+    public FileManager getFileManager() throws RollerException
+    {
+        if (mFileManager == null)
+        {
+                mFileManager = new FileManagerImpl();
+        }
+        return mFileManager;
+    }
+
+    /**
+     * @see org.roller.model.Roller#getThreadManager()
+     */
+    public ThreadManager getThreadManager() throws RollerException
+    {
+        if (mThreadManager == null)
+        {
+            mThreadManager = new ThreadManagerImpl();
+        }
+        return mThreadManager;
+    }
+
+    /**
+     * @see org.roller.model.Roller#getIndexManager()
+     */
+    public IndexManager getIndexManager() throws RollerException
+    {
+        if (mIndexManager == null)
+        {
+            String indexDir = RollerConfig.getProperty("search.index.dir");
+            if (indexDir.indexOf("${user.home}") != -1)
+            {
+                indexDir = StringUtils.replace(
+                        indexDir, "${user.home}",
+                        System.getProperty("user.home"));
+            }
+            if (mLogger.isDebugEnabled())
+            {
+                mLogger.debug("index dir: " + indexDir);
+            }
+            mIndexManager = new IndexManagerImpl(indexDir, this.getThreadManager());
+        }
+        return mIndexManager;
+    }
+    
+    
+    public void release()
+    {
+        if (mFileManager != null) mFileManager.release();
+        if (mThreadManager != null) mThreadManager.release();
+    }
+    
+    
+    public void shutdown()
+    {
+        try 
+        {
+            if (mIndexManager != null) mIndexManager.shutdown();
+            if (mThreadManager != null) mThreadManager.shutdown();
+        } 
+        catch(Exception e) 
+        {
+            mLogger.warn(e);
+        }
+    }
+}

Added: incubator/roller/trunk/src/org/roller/business/ThreadManagerImpl.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/business/ThreadManagerImpl.java?rev=189695&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/business/ThreadManagerImpl.java (added)
+++ incubator/roller/trunk/src/org/roller/business/ThreadManagerImpl.java Wed Jun  8 20:18:46 2005
@@ -0,0 +1,92 @@
+package org.roller.business;
+
+import EDU.oswego.cs.dl.util.concurrent.BoundedBuffer;
+import EDU.oswego.cs.dl.util.concurrent.DirectExecutor;
+import EDU.oswego.cs.dl.util.concurrent.PooledExecutor;
+import EDU.oswego.cs.dl.util.concurrent.ThreadFactory;
+
+import java.util.Date;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import org.roller.model.ThreadManager;
+import org.roller.util.DateUtil;
+
+/**
+ * Manage Roller's background thread use. Currently, Roller starts background
+ * threads for two purposes: 1) the nightly purge of referer counts and 2)
+ * following linkbacks (only occurs if linkbacks are enabled).
+ *
+ * @author aim4min
+ */
+public class ThreadManagerImpl implements ThreadManager
+{
+    private PooledExecutor backgroundExecutor;
+    private DirectExecutor nodelayExecutor;
+    private Timer scheduler;
+
+    public ThreadManagerImpl()
+    {
+        backgroundExecutor = new PooledExecutor(new BoundedBuffer(10), 25);
+        backgroundExecutor.setMinimumPoolSize(4);
+        backgroundExecutor.setKeepAliveTime(1000 * 60 * 5);
+        backgroundExecutor.waitWhenBlocked();
+        backgroundExecutor.createThreads(9);
+
+        backgroundExecutor.setThreadFactory(new ThreadFactory() {
+            public Thread newThread(Runnable command)
+            {
+                Thread t = new Thread(command);
+                t.setDaemon(false);
+                t.setName("Background Execution Threads");
+                t.setPriority(Thread.NORM_PRIORITY);
+
+                return t;
+            }
+        });
+
+        nodelayExecutor = new DirectExecutor();
+        scheduler = new Timer(true);
+    }
+
+    public void executeInBackground(Runnable runnable)
+            throws InterruptedException
+    {
+        backgroundExecutor.execute(runnable);
+    }
+
+    public void executeInForeground(Runnable runnable)
+            throws InterruptedException
+    {
+        nodelayExecutor.execute(runnable);
+    }
+
+    public void scheduleDailyTimerTask(TimerTask task)
+    {
+        scheduler.scheduleAtFixedRate(task,
+                DateUtil.getEndOfDay(new Date()), DateUtil.millisInDay);
+    }
+
+    public void scheduleHourlyTimerTask(TimerTask task)
+    {
+        scheduler.scheduleAtFixedRate(task, new Date(), 60*60*1000);
+    }
+
+    public void scheduleFixedRateTimerTask(TimerTask task, long delayMins, long periodMins) {
+        if (periodMins < MIN_RATE_INTERVAL_MINS) {
+            throw new IllegalArgumentException("Period (" + periodMins +
+                ") shorter than minimum allowed (" + MIN_RATE_INTERVAL_MINS + ")");
+        }
+        scheduler.scheduleAtFixedRate(task, delayMins * 60 * 1000, periodMins * 60 * 1000);
+    }
+
+    public void shutdown()
+    {
+        backgroundExecutor.shutdownAfterProcessingCurrentlyQueuedTasks();
+        scheduler.cancel();
+    }
+
+    public void release()
+    {
+    }
+}
\ No newline at end of file

Added: incubator/roller/trunk/src/org/roller/business/UserManagerImpl.java
URL: http://svn.apache.org/viewcvs/incubator/roller/trunk/src/org/roller/business/UserManagerImpl.java?rev=189695&view=auto
==============================================================================
--- incubator/roller/trunk/src/org/roller/business/UserManagerImpl.java (added)
+++ incubator/roller/trunk/src/org/roller/business/UserManagerImpl.java Wed Jun  8 20:18:46 2005
@@ -0,0 +1,418 @@
+/*
+ * Created on Aug 13, 2003
+ */
+package org.roller.business;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.roller.RollerException;
+import org.roller.model.BookmarkManager;
+import org.roller.model.Roller;
+import org.roller.model.UserManager;
+import org.roller.model.WeblogManager;
+import org.roller.pojos.BookmarkData;
+import org.roller.pojos.FolderData;
+import org.roller.pojos.PageData;
+import org.roller.pojos.RoleData;
+import org.roller.pojos.UserCookieData;
+import org.roller.pojos.UserData;
+import org.roller.pojos.WeblogCategoryData;
+import org.roller.pojos.WebsiteData;
+import org.roller.util.RandomGUID;
+import org.roller.util.Utilities;
+
+import java.io.IOException;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import org.roller.model.RollerFactory;
+
+/**
+ * Abstract base implementation using PersistenceStrategy.
+ * @author Dave Johnson
+ * @author Lance Lavandowska
+ */
+public abstract class UserManagerImpl implements UserManager
+{
+    protected PersistenceStrategy mStrategy;
+    
+    private static Log mLogger =
+        LogFactory.getFactory().getInstance(UserManagerImpl.class);
+    
+    public UserManagerImpl(PersistenceStrategy strategy)
+    {
+        mStrategy = strategy;
+    }
+
+    public void release()
+    {
+    }
+            
+    //--------------------------------------------------------------- Website
+
+    public WebsiteData retrieveWebsite(String id) throws RollerException
+    {
+        return (WebsiteData)mStrategy.load(id,WebsiteData.class);
+    }
+
+    /** 
+     * @see org.roller.model.UserManager#storeWebsite(org.roller.pojos.WebsiteData)
+     */
+    public void storeWebsite(WebsiteData data) throws RollerException
+    {
+        mStrategy.store(data);
+    }
+
+    public void removeWebsite(String id) throws RollerException
+    {
+        mStrategy.remove(id,WebsiteData.class);
+    }
+
+    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+    /** 
+     * This method is a hotspot, it is called on every page request.
+     */
+    public WebsiteData getWebsite(String userName) throws RollerException
+    {
+        return getWebsite(userName, true);
+    } 
+
+    //------------------------------------------------------------------- User
+
+    public UserData retrieveUser(String id) throws RollerException
+    {
+        return (UserData)mStrategy.load(id,UserData.class);
+    }
+
+    public void storeUser(UserData data) throws RollerException
+    {
+        mStrategy.store(data);
+    }
+
+    public void removeUser(String id) throws RollerException
+    {
+        mStrategy.remove(id,UserData.class);
+    }
+
+    //-----------------------------------------------------------------------
+
+    public UserData getUser(String userName) throws RollerException
+    {
+        return getUser(userName, true);
+    }
+
+    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -     
+    public UserData getUser(String userName, boolean enabledOnly) throws RollerException
+    {
+        if (userName==null )
+            throw new RollerException("userName is null");
+        
+        WebsiteData website = getWebsite(userName, enabledOnly);
+        if (website != null)
+        {
+            return website.getUser();
+        }
+        return null;
+    }
+
+    //-----------------------------------------------------------------------
+
+    public List getUsers() throws RollerException
+    {
+        return getUsers(true);
+    }
+
+    //------------------------------------------------------------------------    
+    /** 
+     * @see org.roller.model.UserManager#retrievePage(java.lang.String)
+     */
+    public PageData retrievePageReadOnly(String id) throws RollerException
+    {
+        // Don't hit database for templates stored on disk
+        if (id != null && id.endsWith(".vm")) return null; 
+
+        // Hibernate has a read-only flag: LockMode.READ
+        return (PageData)mStrategy.load(id,PageData.class);
+    }
+
+    //------------------------------------------------------------------- Role
+
+    public RoleData retrieveRole(String id) throws RollerException
+    {
+        return (RoleData)mStrategy.load(id,RoleData.class);
+    }
+
+    public void storeRole(RoleData data) throws RollerException
+    {
+        mStrategy.store(data);
+    }
+
+    public void removeRole(String id) throws RollerException
+    {
+        mStrategy.remove(id,RoleData.class);
+    }
+
+    //------------------------------------------------------------------- Page
+
+    public PageData retrievePage(String id) throws RollerException
+    {
+        // Don't hit database for templates stored on disk
+        if (id != null && id.endsWith(".vm")) return null; 
+
+        return (PageData)mStrategy.load(id,PageData.class);
+    }
+ 
+    public void removePage(String id) throws RollerException
+    {
+        mStrategy.remove(id,PageData.class);
+    }
+
+    public void removePageSafely(String id) throws RollerException
+    {
+        PageData pd = retrievePageReadOnly(id);
+        if (pd == null) return;
+
+        WebsiteData wd = pd.getWebsite();
+        if (pd.getId() == wd.getDefaultPageId()) {
+            mLogger.error("Refusing to remove default page from website of: " +  wd.getUser().getUserName());
+            throw new RollerException(new IllegalArgumentException("Page is default page of website."));
+        }
+        removePage(id);        
+    }
+
+    /**
+     * @see org.roller.model.UserManager#storePage(org.roller.pojos.PageData)
+     */
+    public void storePage(PageData data) throws RollerException
+    {
+        mStrategy.store(data);
+    }
+    
+    public String fixPageLink(PageData data) throws RollerException
+    {
+        String link = Utilities.removeHTML(data.getName());
+        link = Utilities.removeNonAlphanumeric(link);
+
+        data.setLink(link);
+        mStrategy.store( data );
+
+        return link;
+    }
+
+    /**
+     * Add a new Roller user. Store new User, Role, Website, Category, and a
+     * "first post" WeblogEntry in the database. Reads in files from a theme
+     * directory and adds them as Pages for the User's new Website.
+     * 
+     * @param ud  User object representing the new user.
+     * @param themeDir Directory containing the theme for this user
+     */
+    public void addUser(UserData ud, Map pages, String theme, 
+                        String locale, String timezone)
+        throws RollerException
+    {        
+        Roller mRoller = RollerFactory.getRoller();
+        UserManager umgr = mRoller.getUserManager();
+        WeblogManager wmgr = mRoller.getWeblogManager();
+        if (    umgr.getUser(ud.getUserName()) != null 
+             || umgr.getUser(ud.getUserName().toLowerCase()) != null) 
+        {
+            throw new RollerException("error.add.user.userNameInUse");
+        }
+        
+        boolean adminUser = false;
+        List users = this.getUsers();
+        if (users.size() == 0) 
+        {
+            // Make first user an admin
+            adminUser = true;
+        }
+        
+        mStrategy.store(ud);
+        
+        RoleData rd = new RoleData(null, ud, "editor");
+        mStrategy.store(rd);
+        
+        //
+        // CREATE WEBSITE AND CATEGORIES FOR USER
+        //
+        
+        WebsiteData website = new WebsiteData(null,
+            ud.getFullName()+"'s Weblog", // name
+            ud.getFullName()+"'s Weblog", // description
+            ud,                // userId
+            "dummy",           // defaultPageId
+            "dummy",           // weblogDayPageId
+            Boolean.TRUE,      // enableBloggerApi
+            null,                // bloggerCategory
+            null,                // defaultCategory
+            "editor-text.jsp", // editorPage
+            "",                // ignoreWords
+            Boolean.TRUE,      // allowComments  
+            Boolean.FALSE,     // emailComments
+            "",                // emailFromAddress
+            Boolean.TRUE);     // isEnabled
+        website.setEditorTheme(theme);
+        website.setLocale(locale);
+        website.setTimezone(timezone);
+        website.save();
+
+        WeblogCategoryData rootCat = wmgr.createWeblogCategory(
+            website, // websiteId
+            null,   // parent
+            "root",  // name
+            "root",  // description
+            null ); // image
+        rootCat.save();
+        
+        WeblogCategoryData generalCat = wmgr.createWeblogCategory(
+            website,         // websiteId
+            rootCat,
+            "General",       // name
+            "General",       // description
+            null );         // image
+        generalCat.save();
+            
+        WeblogCategoryData javaCat = wmgr.createWeblogCategory(
+            website,         // websiteId
+            rootCat,
+            "Java",          // name
+            "Java",          // description
+            null );          // image
+        javaCat.save();
+            
+        WeblogCategoryData musicCat = wmgr.createWeblogCategory(
+            website,         // websiteId
+            rootCat,
+            "Music",         // name
+            "Music",         // description
+            null );         // image
+        musicCat.save();
+        
+        website.setBloggerCategory(rootCat);
+        website.setDefaultCategory(rootCat);
+        
+        Integer zero = new Integer(0);
+        
+        BookmarkManager bmgr = mRoller.getBookmarkManager();
+                    
+        FolderData root = bmgr.createFolder(
+            null, "root", "root", website);
+        root.save();
+
+        FolderData blogroll = bmgr.createFolder(
+            root, "Blogroll", "Blogroll", website);
+        blogroll.save();
+
+        BookmarkData b1 = bmgr.createBookmark(
+            blogroll, "Dave Johnson", "",
+            "http://rollerweblogger.org/page/roller",
+            "http://rollerweblogger.org/rss/roller",
+            zero, zero, null);
+        b1.save();
+
+        BookmarkData b2 = bmgr.createBookmark(
+            blogroll, "Matt Raible", "",
+            "http://raibledesigns.com/page/rd",
+            "http://raibledesigns.com/rss/rd",
+            zero, zero, null);
+        b2.save();
+
+        BookmarkData b3 = bmgr.createBookmark(
+            blogroll, "Lance Lavandowska", "",
+            "http://brainopolis.dnsalias.com/roller/page/lance/",
+            "http://brainopolis.dnsalias.com/roller/rss/lance/",
+            zero, zero, null);
+        b3.save();
+        
+        
+        FolderData news = bmgr.createFolder(
+            root, "News", "News", website);
+        news.save();
+
+        BookmarkData b5 = bmgr.createBookmark(
+            news, "CNN", "",
+            "http://www.cnn.com",
+            "",
+            zero, zero, null);
+        b5.save();
+
+        BookmarkData b6 = bmgr.createBookmark(
+            news, "NY Times", "", 
+           "http://nytimes.com",
+           "",
+            zero, zero, null);
+        b6.save();
+
+        //
+        // READ THEME FILES AND CREATE PAGES FOR USER
+        //
+        Iterator iter = pages.keySet().iterator();
+        while ( iter.hasNext() )
+        {
+            String pageName = (String) iter.next();
+            String sb = (String)pages.get( pageName );
+              
+            // Store each Velocity template as a page
+            PageData pd = new PageData( null,
+                website,         // website
+                pageName,        // name
+                pageName,        // description
+                pageName,        // link
+                sb,              // template
+                new Date()       // updateTime                
+            );
+            mStrategy.store(pd);
+            
+            if ( pd.getName().equals("Weblog") )
+            {  
+                website.setDefaultPageId(pd.getId());                 
+            }
+            else if ( pd.getName().equals("_day") )
+            {
+                website.setWeblogDayPageId(pd.getId());                 
+            }                
+        }
+        
+        if (adminUser) ud.grantRole("admin");
+        
+        // Save website with blogger cat id, defauld page id and day id
+        mStrategy.store(website); 
+    }
+    
+    /**
+     * @see org.roller.model.UserManager#createLoginCookie(java.lang.String)
+     */
+    public String createLoginCookie(String username) throws RollerException 
+    {
+        UserCookieData cookie = new UserCookieData();
+        cookie.setUsername(username);
+
+        return saveLoginCookie(cookie);
+    }
+
+    /**
+     * Convenience method to set a unique cookie id and save to database
+     * 
+     * @param cookie
+     * @return
+     * @throws Exception
+     */
+    protected String saveLoginCookie(UserCookieData cookie) throws RollerException 
+    {
+        cookie.setCookieId(new RandomGUID().toString());
+        cookie.save();
+
+        String cookieString = null;
+        try {
+            cookieString = Utilities.encodeString(cookie.getUsername() + "|" +
+            		       cookie.getCookieId());
+        } catch (IOException io) {
+        	mLogger.warn("Failed to encode rememberMe cookieString");
+            mLogger.warn(io.getMessage());  
+            cookieString = cookie.getUsername() + "|" + cookie.getCookieId();
+        }
+        return cookieString;
+    }
+}