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

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

Added: incubator/roller/branches/roller_1.x/src/org/roller/business/HitCountData.java
URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_1.x/src/org/roller/business/HitCountData.java?rev=327589&view=auto
==============================================================================
--- incubator/roller/branches/roller_1.x/src/org/roller/business/HitCountData.java (added)
+++ incubator/roller/branches/roller_1.x/src/org/roller/business/HitCountData.java Fri Oct 21 14:27:36 2005
@@ -0,0 +1,167 @@
+package org.roller.business;
+
+/**
+ * Hit count bean.
+ * @author David M Johnson
+ *
+ * @castor:class name="HitCount" table="referer" id="id" key-generator="UUID"
+ * @hibernate.class table="referer"
+ */
+public class HitCountData
+   extends org.roller.pojos.PersistentObject
+   implements java.io.Serializable
+{
+   static final long serialVersionUID = -7998453141931097201L;
+
+   protected java.lang.String id;
+   protected java.lang.Integer dayHitCount;
+   protected java.lang.Integer totalHitCount;
+   protected org.roller.pojos.WebsiteData website;
+
+   public HitCountData()
+   {
+   }
+
+   public HitCountData( java.lang.String id,
+        java.lang.Integer dayHitCount, java.lang.Integer totalHitCount )
+   {
+      this.id = id;
+      this.totalHitCount = totalHitCount;
+      this.dayHitCount = dayHitCount;
+   }
+
+   public HitCountData( HitCountData otherData )
+   {
+      this.id = otherData.id;
+      this.totalHitCount = otherData.totalHitCount;
+      this.dayHitCount = otherData.dayHitCount;
+   }
+
+   /** @castor:field set-method="setId"
+     * @castor:field-sql name="id" sql-dirty="check" dirty="check"
+     * @castor:field-xml node="attribute"
+     * @hibernate.id column="id" type="string"
+     *  generator-class="uuid.hex" unsaved-value="null"
+     */
+   public java.lang.String getId()
+   {
+      return this.id;
+   }
+   public void setId( java.lang.String id )
+   {
+      this.id = id;
+   }
+
+   /** Day hit count.
+     * @castor:field set-method="setDayHitCount"
+     * @castor:field-sql name="dayhits" sql-dirty="check" dirty="check" 
+     * @hibernate.property column="dayhits" non-null="true" unique="false"
+     */
+   public java.lang.Integer getDayHitCount()
+   {
+      return this.dayHitCount;
+   }
+   public void setDayHitCount( java.lang.Integer dayHitCount )
+   {
+      this.dayHitCount = dayHitCount;
+   }
+
+   /** Total hit count.
+     * @castor:field set-method="setTotalHitCount"
+     * @castor:field-sql name="totalhits" sql-dirty="check" dirty="check" 
+     * @hibernate.property column="totalhits" non-null="true" unique="false"
+     */
+   public java.lang.Integer getTotalHitCount()
+   {
+      return this.totalHitCount;
+   }   
+   public void setTotalHitCount( java.lang.Integer hitCount )
+   {
+      this.totalHitCount = hitCount;
+   }
+
+   /** 
+    * ID of website that this referer refers to.
+    * @hibernate.many-to-one column="websiteid" cascade="none" not-null="true"
+    */
+   public org.roller.pojos.WebsiteData getWebsite()
+   {
+      return this.website;
+   }
+   public void setWebsite( org.roller.pojos.WebsiteData website )
+   {
+      this.website = website;
+   }
+
+   public String toString()
+   {
+      StringBuffer str = new StringBuffer("{");
+
+      str.append("id=" + id + " " + "dayHitCount=" + dayHitCount);
+      str.append('}');
+
+      return(str.toString());
+   }
+
+   public boolean equals( Object pOther )
+   {
+      if( pOther instanceof HitCountData )
+      {
+         HitCountData lTest = (HitCountData) pOther;
+         boolean lEquals = true;
+
+         if( this.id == null )
+         {
+            lEquals = lEquals && ( lTest.id == null );
+         }
+         else
+         {
+            lEquals = lEquals && this.id.equals( lTest.id );
+         }
+         if( this.dayHitCount == null )
+         {
+            lEquals = lEquals && ( lTest.dayHitCount == null );
+         }
+         else
+         {
+            lEquals = lEquals && this.dayHitCount.equals( lTest.dayHitCount );
+         }
+         if( this.totalHitCount == null )
+         {
+            lEquals = lEquals && ( lTest.totalHitCount == null );
+         }
+         else
+         {
+            lEquals= lEquals && this.totalHitCount.equals(lTest.totalHitCount);
+         }
+
+         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.dayHitCount != null) ? this.dayHitCount.hashCode() : 0);
+      result = 37*result + ((this.totalHitCount != null) ? this.totalHitCount.hashCode() : 0);
+      return result;
+      }
+
+   /**
+	* Setter is needed in RollerImpl.storePersistentObject()
+    */
+   public void setData( org.roller.pojos.PersistentObject otherData )
+   {
+
+      this.id = ((HitCountData)otherData).id;
+
+      this.dayHitCount = ((HitCountData)otherData).dayHitCount;
+      this.totalHitCount = ((HitCountData)otherData).totalHitCount;
+   }
+
+}

Added: incubator/roller/branches/roller_1.x/src/org/roller/business/IndexManagerImpl.java
URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_1.x/src/org/roller/business/IndexManagerImpl.java?rev=327589&view=auto
==============================================================================
--- incubator/roller/branches/roller_1.x/src/org/roller/business/IndexManagerImpl.java (added)
+++ incubator/roller/branches/roller_1.x/src/org/roller/business/IndexManagerImpl.java Fri Oct 21 14:27:36 2005
@@ -0,0 +1,431 @@
+/*
+ * 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.pojos.UserData;
+import org.roller.pojos.WeblogEntryData;
+
+import EDU.oswego.cs.dl.util.concurrent.ReadWriteLock;
+import EDU.oswego.cs.dl.util.concurrent.WriterPreferenceReadWriteLock;
+import org.roller.config.RollerConfig;
+import org.roller.model.RollerFactory;
+import org.roller.util.StringUtils;
+
+/**
+ * Lucene implementation of IndexManager. This is the central entry point into the Lucene
+ * searching API.
+ * @author aim4min
+ * @author mraible (formatting and making indexDir configurable)
+ */
+public class IndexManagerImpl implements IndexManager
+{
+    //~ Static fields/initializers
+    // =============================================
+
+    private IndexReader reader;
+
+    static Log mLogger = LogFactory.getFactory().getInstance(
+            IndexManagerImpl.class);
+
+    //~ Instance fields
+    // ========================================================
+
+    private boolean searchEnabled = true;
+
+    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()
+    {
+        // check config to see if the internal search is enabled
+        String enabled = RollerConfig.getProperty("search.enabled");
+        if("false".equalsIgnoreCase(enabled))
+            this.searchEnabled = false;
+        
+        // we also need to know what our index directory is
+        // Note: system property expansion is now handled by RollerConfig
+        String indexDir = RollerConfig.getProperty("search.index.dir");
+
+        this.indexDir = indexDir.replace('/', File.separatorChar);
+        
+        // a little debugging
+        mLogger.info("search enabled: " + this.searchEnabled);
+        mLogger.info("index dir: " + this.indexDir);
+
+        String test = indexDir + File.separator + ".index-inconsistent";
+        indexConsistencyMarker = new File(test);
+
+        // only setup the index if search is enabled
+        if (this.searchEnabled) 
+        {
+            
+            // 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));
+                }
+            }
+            
+            if (isInconsistentAtStartup())
+            {
+                mLogger.info(
+                    "Index was inconsistent. Rebuilding index in the background...");
+                try 
+                {                    
+                    rebuildUserIndex();
+                }
+                catch (RollerException e) 
+                {
+                    mLogger.error("ERROR: scheduling re-index operation");
+                }
+            }       
+        }
+    }
+
+    //~ 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();
+    }
+
+    private void scheduleIndexOperation(final IndexOperation op)
+    {
+        try
+        {
+            // only if search is enabled
+            if(this.searchEnabled) {
+                mLogger.debug("Starting scheduled index operation: "+op.getClass().getName());
+                RollerFactory.getRoller().getThreadManager().executeInBackground(op);
+            }
+        }
+        catch (RollerException re)
+        {
+            mLogger.error("Error getting thread manager", re);
+        }
+        catch (InterruptedException e)
+        {
+            mLogger.error("Error executing operation", e);
+        }
+    }
+
+    /**
+     * @param search
+     */
+    public void executeIndexOperationNow(final IndexOperation op)
+    {
+        try
+        {
+            // only if search is enabled
+            if(this.searchEnabled) {
+                mLogger.debug("Executing index operation now: "+op.getClass().getName());
+                RollerFactory.getRoller().getThreadManager().executeInForeground(op);
+            }
+        }
+        catch (RollerException re)
+        {
+            mLogger.error("Error getting thread manager", re);
+        }
+        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/branches/roller_1.x/src/org/roller/business/PersistenceStrategy.java
URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_1.x/src/org/roller/business/PersistenceStrategy.java?rev=327589&view=auto
==============================================================================
--- incubator/roller/branches/roller_1.x/src/org/roller/business/PersistenceStrategy.java (added)
+++ incubator/roller/branches/roller_1.x/src/org/roller/business/PersistenceStrategy.java Fri Oct 21 14:27:36 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/branches/roller_1.x/src/org/roller/business/PingQueueManagerImpl.java
URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_1.x/src/org/roller/business/PingQueueManagerImpl.java?rev=327589&view=auto
==============================================================================
--- incubator/roller/branches/roller_1.x/src/org/roller/business/PingQueueManagerImpl.java (added)
+++ incubator/roller/branches/roller_1.x/src/org/roller/business/PingQueueManagerImpl.java Fri Oct 21 14:27:36 2005
@@ -0,0 +1,58 @@
+/*
+ * 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;
+
+/**
+ * Abstract implementation of PingQueueManager.
+ */
+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/branches/roller_1.x/src/org/roller/business/PingTargetManagerImpl.java
URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_1.x/src/org/roller/business/PingTargetManagerImpl.java?rev=327589&view=auto
==============================================================================
--- incubator/roller/branches/roller_1.x/src/org/roller/business/PingTargetManagerImpl.java (added)
+++ incubator/roller/branches/roller_1.x/src/org/roller/business/PingTargetManagerImpl.java Fri Oct 21 14:27:36 2005
@@ -0,0 +1,147 @@
+/*
+ * 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;
+
+/**
+ * Abstract implementation of PingTargetManager.
+ */
+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/branches/roller_1.x/src/org/roller/business/PlanetManagerImpl.java
URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_1.x/src/org/roller/business/PlanetManagerImpl.java?rev=327589&view=auto
==============================================================================
--- incubator/roller/branches/roller_1.x/src/org/roller/business/PlanetManagerImpl.java (added)
+++ incubator/roller/branches/roller_1.x/src/org/roller/business/PlanetManagerImpl.java Fri Oct 21 14:27:36 2005
@@ -0,0 +1,230 @@
+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;
+    
+    // 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);
+    
+    // Cache up to 20 aggregations, each for up to 30 minutes
+    // TODO: make this top-subscriptions cache configurable
+    protected LRUCache2 topSubscriptionsByGroup = 
+        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); 
+                if (roller != null) roller.commit();
+            }
+            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");
+    }
+
+}

Added: incubator/roller/branches/roller_1.x/src/org/roller/business/PropertiesManagerImpl.java
URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_1.x/src/org/roller/business/PropertiesManagerImpl.java?rev=327589&view=auto
==============================================================================
--- incubator/roller/branches/roller_1.x/src/org/roller/business/PropertiesManagerImpl.java (added)
+++ incubator/roller/branches/roller_1.x/src/org/roller/business/PropertiesManagerImpl.java Fri Oct 21 14:27:36 2005
@@ -0,0 +1,247 @@
+/*
+ * 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();
+        
+        // can't do initialization without our config defs
+        if(runtimeConfigDefs == null)
+            return props;
+        
+        // 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/branches/roller_1.x/src/org/roller/business/PubTimeData.java
URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_1.x/src/org/roller/business/PubTimeData.java?rev=327589&view=auto
==============================================================================
--- incubator/roller/branches/roller_1.x/src/org/roller/business/PubTimeData.java (added)
+++ incubator/roller/branches/roller_1.x/src/org/roller/business/PubTimeData.java Fri Oct 21 14:27:36 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/branches/roller_1.x/src/org/roller/business/RefererManagerImpl.java
URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_1.x/src/org/roller/business/RefererManagerImpl.java?rev=327589&view=auto
==============================================================================
--- incubator/roller/branches/roller_1.x/src/org/roller/business/RefererManagerImpl.java (added)
+++ incubator/roller/branches/roller_1.x/src/org/roller/business/RefererManagerImpl.java Fri Oct 21 14:27:36 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/branches/roller_1.x/src/org/roller/business/RollerImpl.java
URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_1.x/src/org/roller/business/RollerImpl.java?rev=327589&view=auto
==============================================================================
--- incubator/roller/branches/roller_1.x/src/org/roller/business/RollerImpl.java (added)
+++ incubator/roller/branches/roller_1.x/src/org/roller/business/RollerImpl.java Fri Oct 21 14:27:36 2005
@@ -0,0 +1,112 @@
+/*
+ * 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.model.FileManager;
+import org.roller.model.IndexManager;
+import org.roller.model.Roller;
+import org.roller.model.ThemeManager;
+import org.roller.model.ThreadManager;
+
+
+
+/**
+ * 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;
+    protected ThemeManager    mThemeManager = 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)
+        {
+            mIndexManager = new IndexManagerImpl();
+        }
+        return mIndexManager;
+    }
+    
+    /**
+     * @see org.roller.model.Roller#getThemeManager()
+     */
+    public ThemeManager getThemeManager() throws RollerException
+    {
+        if (mThemeManager == null)
+        {
+            mThemeManager = new ThemeManagerImpl();
+        }
+        return mThemeManager;
+    }
+    
+    
+    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/branches/roller_1.x/src/org/roller/business/ThemeManagerImpl.java
URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_1.x/src/org/roller/business/ThemeManagerImpl.java?rev=327589&view=auto
==============================================================================
--- incubator/roller/branches/roller_1.x/src/org/roller/business/ThemeManagerImpl.java (added)
+++ incubator/roller/branches/roller_1.x/src/org/roller/business/ThemeManagerImpl.java Fri Oct 21 14:27:36 2005
@@ -0,0 +1,276 @@
+/*
+ * ThemeManagerImpl.java
+ *
+ * Created on June 27, 2005, 1:33 PM
+ */
+
+package org.roller.business;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.FilenameFilter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.roller.RollerException;
+import org.roller.ThemeNotFoundException;
+import org.roller.config.RollerConfig;
+import org.roller.model.ThemeManager;
+import org.roller.pojos.Theme;
+import org.roller.pojos.ThemeTemplate;
+
+
+/**
+ * Base implementation of a ThemeManager.
+ * 
+ * This particular implementation reads theme data off the filesystem 
+ * and assumes that those themes are not changable at runtime.
+ *
+ * @author Allen Gilliland
+ */
+public class ThemeManagerImpl implements ThemeManager {
+    
+    private static Log mLogger = 
+        LogFactory.getFactory().getInstance(ThemeManagerImpl.class);
+    
+    private Map themes;
+    
+    
+    protected ThemeManagerImpl() {
+        
+        // rather than be lazy we are going to load all themes from
+        // the disk preemptively during initialization and cache them
+        mLogger.debug("Initializing ThemeManagerImpl");
+        
+        this.themes = this.loadAllThemesFromDisk();
+        mLogger.info("Loaded "+this.themes.size()+" themes from disk.");
+    }
+    
+    
+    /**
+     * @see org.roller.model.ThemeManager#getTheme(java.lang.String)
+     */
+    public Theme getTheme(String name) 
+        throws ThemeNotFoundException, RollerException {
+        
+        Theme theme = (Theme) this.themes.get(name);
+        if(theme == null)
+            throw new ThemeNotFoundException("Couldn't find theme ["+name+"]");
+        
+        return theme;
+    }
+    
+    
+    /**
+     * @see org.roller.model.ThemeManager#getThemeById(java.lang.String)
+     */
+    public Theme getThemeById(String id) 
+        throws ThemeNotFoundException, RollerException {
+        
+        // In this implementation where themes come from the filesystem we
+        // know that the name and id for a theme are the same
+        return this.getTheme(id);
+    }
+    
+    
+    /**
+     * @see org.roller.model.ThemeManager#getThemesList()
+     */
+    public List getThemesList() {
+        
+        List themes = new ArrayList(this.themes.keySet());
+        
+        // sort 'em ... the natural sorting order for Strings is alphabetical
+        Collections.sort(themes);
+        
+        return themes;
+    }
+    
+    
+    /**
+     * @see org.roller.model.ThemeManager#getEnabledThemesList()
+     */
+    public List getEnabledThemesList() {
+        
+        Collection all_themes = this.themes.values();
+        
+        // make a new list of only the enabled themes
+        List enabled_themes = new ArrayList();
+        Iterator it = all_themes.iterator();
+        Theme theme = null;
+        while(it.hasNext()) {
+            theme = (Theme) it.next();
+            if(theme.isEnabled())
+                enabled_themes.add(theme.getName());
+        }
+                
+        // sort 'em ... the natural sorting order for Strings is alphabetical
+        Collections.sort(enabled_themes);
+        
+        return enabled_themes;
+    }
+    
+    
+    /**
+     * @see org.roller.model.ThemeManager#getTemplate(String, String)
+     */
+    public ThemeTemplate getTemplate(String theme_name, String template_name)
+        throws ThemeNotFoundException, RollerException {
+        
+        // basically we just try and lookup the theme first, then template
+        Theme theme = this.getTheme(theme_name);
+        
+        return theme.getTemplate(template_name);
+    }
+    
+    
+    /**
+     * @see org.roller.model.ThemeManager#getTemplateById(java.lang.String)
+     */
+    public ThemeTemplate getTemplateById(String id)
+        throws ThemeNotFoundException, RollerException {
+        
+        if(id == null)
+            throw new ThemeNotFoundException("Theme id was null");
+        
+        // in our case we expect a template id to be <theme>:<template>
+        // so extract each piece and do the lookup
+        String[] split = id.split(":",  2);
+        if(split.length != 2)
+            throw new ThemeNotFoundException("Invalid theme id ["+id+"]");
+        
+        return this.getTemplate(split[0], split[1]);
+    }
+    
+    
+    /**
+     * @see org.roller.model.ThemeManager#getTemplateByLink(java.lang.String)
+     */
+    public ThemeTemplate getTemplateByLink(String theme_name, String template_link)
+        throws ThemeNotFoundException, RollerException {
+        
+        // basically we just try and lookup the theme first, then template
+        Theme theme = this.getTheme(theme_name);
+        
+        return theme.getTemplateByLink(template_link);
+    }
+    
+    
+    /**
+     * This is a convenience method which loads all the theme data from
+     * themes stored on the filesystem in the roller webapp /themes/ directory.
+     */
+    private Map loadAllThemesFromDisk() {
+        
+        Map themes = new HashMap();
+        
+        // NOTE: we need to figure out how to get the roller context path
+        String themespath = RollerConfig.getProperty("context.realpath");
+        if(themespath.endsWith(File.separator))
+            themespath += "themes";
+        else
+            themespath += File.separator + "themes";
+        
+        // first, get a list of the themes available
+        File themesdir = new File(themespath);
+        FilenameFilter filter = new FilenameFilter() {
+            public boolean accept(File dir, String name) {
+                File file =
+                        new File(dir.getAbsolutePath() + File.separator + name);
+                return file.isDirectory();
+            }
+        };
+        String[] themenames = themesdir.list(filter);
+        
+        if(themenames == null)
+            themenames = new String[0];
+        
+        // now go through each theme and read all it's templates
+        Theme theme = null;
+        for(int i=0; i < themenames.length; i++) {
+            theme = this.loadThemeFromDisk(themenames[i], 
+                        themespath + File.separator + themenames[i]);
+            
+            themes.put(theme.getName(), theme);
+        }
+        
+        return themes;
+    }
+    
+    
+    /**
+     * Another convenience method which knows how to load a single theme
+     * off the filesystem and return a Theme object
+     */
+    private Theme loadThemeFromDisk(String theme_name, String themepath) {
+        
+        mLogger.debug("Loading theme "+theme_name+" from "+themepath);
+        
+        Theme theme = new Theme();
+        theme.setName(theme_name);
+        theme.setAuthor("Roller");
+        theme.setLastEditor("Roller");
+        theme.setEnabled(true);
+        
+        // start by getting a list of the .vm files for this theme
+        File themedir = new File(themepath);
+        FilenameFilter filter = new FilenameFilter()
+        {
+            public boolean accept(File dir, String name)
+            {
+                return name.endsWith(".vm");
+            }
+        };
+        String[] templates = themedir.list(filter);
+        
+        // go through each .vm file and read in its contents to a ThemeTemplate
+        String template_name = null;
+        ThemeTemplate theme_template = null;
+        for (int i=0; i < templates.length; i++) {
+            // strip off the .vm part
+            template_name = templates[i].substring(0, templates[i].length() - 3);
+            
+            try {
+                File template_file = new File(themepath + File.separator + templates[i]);
+                
+                if(!template_file.exists() && !template_file.canRead()) {
+                    mLogger.warn("Couldn't read theme template file ["+template_file.getPath()+"]");
+                    continue;
+                }
+                
+                FileReader reader = new FileReader(template_file);
+                char[] chars = new char[(int) template_file.length()];
+                reader.read(chars);
+                
+                // construct ThemeTemplate representing this file
+                theme_template = new ThemeTemplate(
+                        theme_name+":"+template_name,
+                        template_name,
+                        template_name,
+                        new String(chars),
+                        template_name,
+                        new Date(template_file.lastModified()));
+                
+                // add it to the theme
+                theme.setTemplate(template_name, theme_template);
+                
+            } catch (Exception e) {
+                // warn?
+            }
+        }
+        
+        // use the last mod date of the last template file
+        // as the last mod date of the theme
+        theme.setLastModified(theme_template.getLastModified());
+        
+        return theme;
+    }
+
+}

Added: incubator/roller/branches/roller_1.x/src/org/roller/business/ThreadManagerImpl.java
URL: http://svn.apache.org/viewcvs/incubator/roller/branches/roller_1.x/src/org/roller/business/ThreadManagerImpl.java?rev=327589&view=auto
==============================================================================
--- incubator/roller/branches/roller_1.x/src/org/roller/business/ThreadManagerImpl.java (added)
+++ incubator/roller/branches/roller_1.x/src/org/roller/business/ThreadManagerImpl.java Fri Oct 21 14:27:36 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