/*
 * Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 */
package com.arsdigita.forum;

import java.math.BigDecimal;

import org.apache.log4j.Logger;

import com.arsdigita.categorization.Category;
import com.arsdigita.cms.lifecycle.LifecycleDefinition;
import com.arsdigita.domain.DataObjectNotFoundException;
import com.arsdigita.domain.DomainObjectFactory;
import com.arsdigita.kernel.EmailAddress;
import com.arsdigita.kernel.Group;
import com.arsdigita.kernel.Kernel;
import com.arsdigita.kernel.KernelExcursion;
import com.arsdigita.kernel.Party;
import com.arsdigita.kernel.permissions.PermissionDescriptor;
import com.arsdigita.kernel.permissions.PermissionService;
import com.arsdigita.kernel.permissions.PrivilegeDescriptor;
import com.arsdigita.messaging.MessageThread;
import com.arsdigita.persistence.DataAssociation;
import com.arsdigita.persistence.DataAssociationCursor;
import com.arsdigita.persistence.DataCollection;
import com.arsdigita.persistence.DataObject;
import com.arsdigita.persistence.DataQuery;
import com.arsdigita.persistence.Filter;
import com.arsdigita.persistence.FilterFactory;
import com.arsdigita.persistence.OID;
import com.arsdigita.persistence.Session;
import com.arsdigita.persistence.SessionManager;
import com.arsdigita.util.Assert;
import com.arsdigita.web.Application;

/**
 * The Forum class represents a discussion forum.
 *
 * @author Kevin Scaldeferri (kevin@arsdigita.com)
 *
 * @version $Revision: 1.7 $ $Author: chrisg23 $ $DateTime: 2004/08/17 23:26:27 $
 */

public class Forum extends Application {
	public static final String THREAD_SUBSCRIPTION_GROUPS_NAME = "Thread Subscription Groups";
    public static final String versionId =
		"$Id: Forum.java 1628 2007-09-17 08:10:40Z chrisg23 $"
			+ "$Author: chrisg23 $"
			+ "$DateTime: 2004/08/17 23:26:27 $";

    private static ForumConfig s_config = new ForumConfig();

    static {
        s_config.load();
    }

    public static ForumConfig getConfig() {
        return s_config;
    }


    private static final Logger s_log = Logger.getLogger(Forum.class);

    public static final String BASE_DATA_OBJECT_TYPE =
        "com.arsdigita.forum.Forum";

    public static final String PACKAGE_TYPE = "forum";
	//////
	//Forum specific privileges
	/////
	public static final String FORUM_MODERATION_PRIVILEGE = "forum_moderation";
	public static final String CREATE_THREAD_PRIVILEGE = "forum_create_thread";
	public static final String RESPOND_TO_THREAD_PRIVILEGE = "forum_respond";
	// separate read privilege required because all public users 
	// have READ on homepage, which is parent of forum, hence
	// everyone inherits READ cg
	//
	// note in hindsight, I have stopped homepage being set as
	// permission context for forum, because site search checks 
	// for READ privilege anyway, and so search results were being
	// returned for non public posts. This means there is no longer 
	// any need for a separate forum_read privilege, though it 
	// does no harm. Now removed
	// public static final String FORUM_READ_PRIVILEGE = "forum_read";

	///////
	// pdl forum attribute/association names
	///////
    private static final String POSTS = "posts";
    private static final String SUBSCRIPTIONS = "subscriptions";
    private static final String MODERATION = "isModerated";
    private static final String NOTICEBOARD = "isNoticeboard";
	private static final String ADMIN_GROUP = "adminGroup";
    private static final String MODERATION_GROUP = "moderationGroup";
	private static final String THREAD_CREATE_GROUP = "threadCreateGroup";
	private static final String THREAD_RESPONDER_GROUP = "threadRespondGroup";
	private static final String READ_GROUP = "readGroup";
    private static final String CATEGORY = "category";
    private static final String EXPIRE_AFTER = "expireAfter";
    private static final String LIFECYCLE_DEFINITION = "lifecycleDefinition";
	// additional attributes added chris.gilbert@westsussex.gov.uk 
	private static final String ALLOW_FILE_ATTACHMENTS =
		"fileAttachmentsAllowed";
	private static final String ALLOW_IMAGE_UPLOADS = "imageUploadsAllowed";
	private static final String AUTOSUBSCRIBE_THREAD_STARTER =
		"autoSubscribeThreadStarter";
	private static final String INTRODUCTION = "introduction";
	private static final String NO_CATEGORY_POSTS = "noCategoryPostsAllowed";
	private static final String ANONYMOUS_POSTS = "anonymousPostsAllowed";

    public Forum(DataObject data) {
        super(data);
    }

    public Forum(OID oid) throws DataObjectNotFoundException {
        super(oid);
    }

    public Forum(BigDecimal id) throws DataObjectNotFoundException {
        super(new OID(BASE_DATA_OBJECT_TYPE, id));
    }

    public static Forum create(String urlName, String title,
                               Application parent) {
        return create(urlName, title, parent, false);
    }

    /**
     * This method should be used to create a new Forum object everywhere
     * except in the constructor of a subclass of Forum.
     * This will by default create a new Category which will be the root
     * category for the Forum in the event that the Forum should be
     * categorized.
     * This also sets up instant and daily subscriptions on the Forum.
	 * The default for moderation is false.
	 * 
	 * Also sets default values for other forum settings. These can be 
	 * amended under the setup tab in the ui
     */

    public static Forum create(String urlName, String title,
                               Application parent, boolean moderated) {
        s_log.debug("creating forum " + title);
        Forum forum = (Forum) Application.createApplication
		    (BASE_DATA_OBJECT_TYPE, urlName, title, parent, true);

        forum.setModerated(moderated);
		// default settings ensure legacy forum users do not 
		// see any change  chris.gilbert@westsussex.gov.uk
		forum.setAllowFileAttachments(false);
		forum.setAllowImageUploads(false);
		forum.setAutoSubscribeThreadCreator(false);
		forum.setNoCategoryPostsAllowed(true);
		forum.setAnonymousPostsAllowed(false);

        return forum;
    }

    /**
     * Sets Root Category for forum
     */
    private void setRootCategory(Category category) {
        Assert.exists(category, Category.class);
        setAssociation(CATEGORY, category);
    }

    /**
     * @return the Root Category for this forum, or creates a new one
     * does not have a root category, and returns it.
     */

    public Category getRootCategory() {
        DataObject category = (DataObject) get(CATEGORY);
        if (category == null) {
            return createRootCategory();
        } else {
            return (Category)DomainObjectFactory
                .newInstance(category);
        }
    }

    /**
	 * Set introduction
	 */
	public void setIntroduction(String introduction) {
		set(INTRODUCTION, introduction);
	}

	/**
	 * @return introduction
	 */

	public String getIntroduction() {
		return (String) get(INTRODUCTION);

	}

	/**
     * creates a Root Category for the forum.
     */
    private Category createRootCategory() {
        Category category = new Category(getTitle(),
                                         "Root category for forum "
                                         + getTitle());
        category.save();
        setRootCategory(category);
        return category;
    }

	private void createGroups() {
		Group administrators = new Group();
		administrators.setName(getTitle() + " Administrators");
		setAssociation(ADMIN_GROUP, administrators);

        Group moderators = new Group();
        moderators.setName(getTitle() + " Moderators");
        setAssociation( MODERATION_GROUP, moderators );
        // This is bit of a hack.  For moderator messages to be sent out properly,
        // moderator group has to have proper primary email address.  Which means
        // it has to be unique among all primary email addresses.  Failing to the
        // that, we block *all* emails going out from CCM (whether they're generated
        // by Forum app doesn't matter), since SimpleQueueManager will encounter
        // NPE when trying to retrieve sender's email address, thus stopping any
        // further message processing.
        //   Actually, the only hack involved is making the email address unique.
        String email = "forum-moderator-" + getID() + "-" + moderators.getID() + "@" + s_config.getReplyHostName();
        moderators.setPrimaryEmail(new EmailAddress(email));

		// chris.gilbert@westsussex.gov.uk create additional groups for privilege
		// assignment - could have assigned privileges directly without having associated 
		// groups, but this reduces rows in the (already enormous) dnm_permissions
		// table
		Group threadCreators = new Group();
		threadCreators.setName(getTitle() + " Thread Creators");
		setAssociation(THREAD_CREATE_GROUP, threadCreators);

		Group threadResponders = new Group();
		threadResponders.setName(getTitle() + " Thread Responders");
		setAssociation(THREAD_RESPONDER_GROUP, threadResponders);

		Group forumReaders = new Group();
		forumReaders.setName(getTitle() + " Readers");
		setAssociation(READ_GROUP, forumReaders);
		
		Group container = getGroup();
		
		container.addSubgroup(administrators);
		container.addSubgroup(moderators);
		container.addSubgroup(threadCreators);
		container.addSubgroup(threadResponders);
		container.addSubgroup(forumReaders);
		Group threadSubscriptions = new Group();
		threadSubscriptions.setName(THREAD_SUBSCRIPTION_GROUPS_NAME);
		container.addSubgroup(threadSubscriptions);
		container.save();
		

		
    }

    public void initialize() {
        super.initialize();

        if (isNew()) {
            setModerated(false);
            setNoticeboard(false);
			setAllowFileAttachments(false);
			setAllowImageUploads(false);
			setAutoSubscribeThreadCreator(false);
			setNoCategoryPostsAllowed(true);
			setAnonymousPostsAllowed(false);
            createRootCategory();
			
        }
    }

    private boolean m_wasNew;

    protected void beforeSave() {
        m_wasNew = isNew();
        super.beforeSave();
    }

    protected void afterSave() {
        if (m_wasNew) {
            PermissionService.setContext(getRootCategory(), this);
			createGroups();
			if (getAdminGroup() != null) {
				PermissionService.grantPermission(
					new PermissionDescriptor(
						PrivilegeDescriptor.ADMIN,
						this,
						getAdminGroup()));
				s_log.debug(
					"Current user : "
						+ Kernel.getContext().getParty().getPrimaryEmail()
						+ " class is "
						+ Kernel.getContext().getParty().getClass());
				//
				// chris.gilbert@westsussex.gov.uk Original plan was that creator of forum  
				// is administrator by default, but party from Kernel at this point in code is 
				// acs-system-party - creation must happen in a KernelExcersion somewhere 
				// though I can't immediately see where.
				// as a consequence, code below justs causes a classcast exception, 
				// 
				// revisit, but in meantime, only site admin can administer new forum
				// until forum admin permissions set in UI
				// 
				// User creator = (User) Kernel.getContext().getParty();
				// can't be null but let's be supercautious
				// if (creator != null) {
				//	getAdminGroup().addMember(creator);
				// }
				///
			}

            if (getModerationGroup() != null ) {
                PermissionService.grantPermission(
                    new PermissionDescriptor(
                        PrivilegeDescriptor.get(FORUM_MODERATION_PRIVILEGE),
                        this,
						getModerationGroup()));
			}
			if (getThreadCreateGroup() != null) {
				PermissionService.grantPermission(
					new PermissionDescriptor(
						PrivilegeDescriptor.get(CREATE_THREAD_PRIVILEGE),
						this,
						getThreadCreateGroup()));
				// chris.gilbert@westsussex.gov.uk 
				// wouldn't do this normally, but this enables legacy implementations
				// to use new version without any side effects
				// public can view forum by default and see create thread link - existing 
				// code forces login if link is selected 
				getThreadCreateGroup().addMember(Kernel.getPublicUser());
			}

			if (getThreadResponderGroup() != null) {
				PermissionService.grantPermission(
					new PermissionDescriptor(
						PrivilegeDescriptor.get(RESPOND_TO_THREAD_PRIVILEGE),
						this,
						getThreadResponderGroup()));
			}

			if (getReadGroup() != null) {
				PermissionService.grantPermission(
					new PermissionDescriptor(
						PrivilegeDescriptor.READ,
						this,
						getReadGroup()));
            }
            KernelExcursion excursion = new KernelExcursion() {
                    protected void excurse() {
                        setParty(Kernel.getSystemParty());
                        // FIXME
                        // Workspace parentWorkspace =
                        //     Workspace.getWorkspaceForApplication
                        //         (Forum.this);
                        //
                        // if (parentWorkspace != null) {
                        //     PermissionService.grantPermission
                        //         (new PermissionDescriptor
                        //          (PrivilegeDescriptor.WRITE,
                        //           Forum.this,
                        //           parentWorkspace.getMemberRole()));
                        // }

                    }
                };
            excursion.run();
        }

        DataCollection subs = null;
        try {
            subs = getSubscriptions();
            if (subs.isEmpty()) {
                createSubscriptions();
            }

        } finally {
            if ( null != subs ) {
                subs.close();
            }
        }


		// chris.gilbert@westsussex.gov.uk line removed.
		// afterSave in Application sets permission
		// context of forum to parent app (portal homepage)
		// don't want to inherit permissions of portal,
		// as public users have 'READ' privilege on this
		// and so get shown postings in search results.
		//
		// 	
		// super.afterSave();
    }

    protected String getBaseDataObjectType() {
        return BASE_DATA_OBJECT_TYPE;
    }


    /**
     * Gets all the Subscriptions associated with this Forum.
     */
    public DataCollection getAllSubscriptions() {
        DataAssociationCursor dac = ((DataAssociation) get(SUBSCRIPTIONS)).cursor();
        return dac;
    }

    public DataCollection getSubscriptions() {
        DataCollection subs = getAllSubscriptions();
        subs.addEqualsFilter("isModerationAlert", Boolean.FALSE);

        return subs;
    }

    public DataCollection getModerationAlerts() {
        DataCollection subs = getAllSubscriptions();
        subs.addEqualsFilter("isModerationAlert", Boolean.TRUE);

        return subs;
    }

    /**
     * <font color="red">Experimental</font>
     * Gets all the messages which have been posted to this forum.
     * We never actually use this method and it may disappear in the
     * near future.  (This method is not actually as useful as it might
     * appear because you don't get important information like the depth
     * of the message in the thread.  OTOH, maybe it should be modified
     * to use a custom DataQuery which does this.)
     */
    public DataAssociation getPosts() {
        return (DataAssociation)get(POSTS);
    }

    /**
	 * gets all pending messages and messages for reapproval - allows 
	 * moderators to see which messages require their attention
	 * @return
	 */
	public DataAssociation getPendingPosts() {
		// doesn't use getPosts in view of the warning that it 
		// may disappear
		DataAssociation posts = (DataAssociation) get(POSTS);
		FilterFactory factory = posts.getFilterFactory();
		Filter pending = factory.equals(Post.STATUS, Post.PENDING);
		Filter reapprove = factory.equals(Post.STATUS, Post.REAPPROVE);
		;

		posts.addFilter(factory.or().addFilter(pending).addFilter(reapprove));

		return posts;
	}

	/**
	 * gets all suppressed messages - allows moderators to see which messages
	 * heve been rejectedrequire their attention
	 * @return
	 */
	public DataAssociation getSuppressedPosts() {
		// doesn't use getPosts in view of the warning that it 
		// may disappear
		DataAssociation posts = (DataAssociation) get(POSTS);
		posts.addEqualsFilter(Post.STATUS, Post.SUPPRESSED);
		return posts;
	}

	/**
     * Gets a ThreadCollection of the threads in this forum.  I.e. the
     * top-level posts which are not replies to any other post.
     */
    public ThreadCollection getThreads() {
        return getThreads((BigDecimal)null);
    }

    /**
     * Gets a ThreadCollection of the threads in this forum.  I.e. the
     * top-level posts which are not replies to any other post. It
     * is filtered to only show approved messages, or those posted
     * by the user
     */
    public ThreadCollection getThreads(Party party) {
        ThreadCollection threads = getThreads();

        if (isModerated() &&
            !canModerate(party)) {
            s_log.debug( "Only showing approved threads" );
            threads.filterUnapproved(party);
        }

        return threads;
    }


    /**
     * Gets a ThreadCollection of the threads in a specific Category.
     */
    public ThreadCollection getThreads(BigDecimal categoryID) {

         DataCollection threadsData
             = SessionManager.getSession().retrieve(
                MessageThread.BASE_DATA_OBJECT_TYPE);


         threadsData.addEqualsFilter("root.objectID", getID());

         if (categoryID != null) {
             // XXX bad dep on ui package
             if (categoryID.equals(com.arsdigita.forum.ui.Constants.TOPIC_NONE)) {
                 Filter f = threadsData.addNotInSubqueryFilter
                     ("root.id", "com.arsdigita.forum.uncategoryObject");
             } else {
                 Filter f = threadsData.addInSubqueryFilter
                     ("root.id", "com.arsdigita.forum.categoryObject");
                 f.set("categoryID", categoryID);
             }
         }

         threadsData.addOrder("lastUpdate desc");

         return new ThreadCollection(threadsData);
    }

    /**
     * Gets a ThreadCollection of the threads in a specific Category.
     * It is filtered to only show approved messages, or those posted
     * by the user
     */
    public ThreadCollection getThreads(BigDecimal categoryID,
                                       Party party) {
        ThreadCollection threads = getThreads(categoryID);

        if (isModerated() &&
            !canModerate(party)) {
            s_log.debug( "Only showing approved threads" );
            threads.filterUnapproved(party);
        }

        return threads;


	}

    /**
     * Sets up instant and daily subscriptions for the forum.  Daily
     * digests will appear to come from the specified user.  The
     * subscriptions are save()d by this method.
     *
     * @param creationUser the User whom daily digests will come from.
     */
    protected void createSubscriptions() {
        s_log.debug("Creating subscriptions!");

        new KernelExcursion() {
            protected void excurse() {
                setParty(Kernel.getSystemParty());

                final Party party = getConfig().getDigestUser();
                Assert.exists(party, Party.class);

                new ForumSubscription(Forum.this).save();
                new DailySubscription(Forum.this, party).save();

                Group moderators = getModerationGroup();
                if ( null != moderators ) {

                    s_log.debug("creatiing moderation subscription "
                                + moderators.getName());

                    new ModerationAlert(Forum.this, moderators).save();
                }
            }
        }.run();
    }

    /**
     * Gets categories and number of posts for the forum.
     *
     * @return DataQuery with category_id, name, number of threads,
     * and last post date
     */
    public DataQuery getCategories() {
        Session session = SessionManager.getSession();
        DataQuery query = session.retrieveQuery
            ("com.arsdigita.forum.getCategorizationSummary");
        query.setParameter("forumID", getID());
        return query;
    }

    /**
     * Gets empty categories for the forum.
     *
     * @return DataQuery with category_id and name
     */
    public DataQuery getEmptyCategories() {
        Session session = SessionManager.getSession();
        DataQuery query = session.retrieveQuery
            ("com.arsdigita.forum.getUnusedCategories");
        query.setParameter("forumID", getID());
        return query;
    }

    /**
     * Gets Uncategory and number of posts for the forum.
     *
     * @return DataQuery with number of threads
     */
    public DataQuery getUnCategory() {
        Session session = SessionManager.getSession();
        DataQuery query = session.retrieveQuery
            ("com.arsdigita.forum.getUncategorizedSummary");
        query.setParameter("forumID", getID());
        return query;
    }

    public DataAssociationCursor getFilledCategories() {
        Category root = getRootCategory();
        DataAssociationCursor cursor =
            root.getRelatedCategories(Category.CHILD);

        Filter f = cursor.addInSubqueryFilter
            ("id", "com.arsdigita.forum.filledCategories");
        return cursor;
    }

    /**
     * Receives category and returns boolean of whether forum has
     * posts in that category.
     *
     * @return boolean
     */
    public boolean hasCategorizedPosts(Category cat) {
        ThreadCollection children = getThreads(cat.getID());
        if (children.size() == 0) {
            return false;
        } else {
            return true;
        }
    }

    /**
     * checks if the user can edit posts in this forum
     */
    public boolean canEdit(Party party) {
        return (getConfig().canAdminEditPosts() &&
                PermissionService.checkPermission(
                    new PermissionDescriptor(PrivilegeDescriptor.EDIT,
                                             this,
                                             party)));
    }

    public boolean canAdminister(Party party) {
        return PermissionService.checkPermission(
            new PermissionDescriptor(PrivilegeDescriptor.ADMIN,
                                     this,
                                     party));
    }

    public boolean canModerate(Party party) {
        return PermissionService.checkPermission(
            new PermissionDescriptor(
                PrivilegeDescriptor.get(FORUM_MODERATION_PRIVILEGE),
                this,
                party));
    }

    /**
     * Enables / disables moderation on the forum. When
     * disabling moderation, all pending posts will be
     * automatically marked as approved.
     */
    public void setModerated(boolean moderate) {
        Boolean old = (Boolean)get(MODERATION);
        if (Boolean.TRUE.equals(old) &&
            !moderate) {

            DataAssociationCursor posts = getPosts().cursor();
            posts.addEqualsFilter(Post.STATUS, Post.PENDING);
            while (posts.next()) {
                Post post
                    = (Post)DomainObjectFactory.newInstance(
                        posts.getDataObject());
                post.setStatus(Post.APPROVED);
                post.save();
            }

        }
        set(MODERATION, new Boolean(moderate));
    }

    public boolean isModerated() {
        Boolean isModerated = (Boolean)get(MODERATION);
        Assert.exists(isModerated);
        return isModerated.booleanValue();
    }

    /**
     *  Enables/disables the noticeboard functionality.
     * If enabled, no replies will be allowed.
     */
    public void setNoticeboard(boolean noticeboard) {
        set(NOTICEBOARD, new Boolean(noticeboard));
    }

    public boolean isNoticeboard() {
        return Boolean.TRUE.equals(get(NOTICEBOARD));
    }

	/** Returns the administrator group. Null if it doesn't exist */
	public Group getAdminGroup() {
		DataObject dObj = (DataObject) get(ADMIN_GROUP);
		Assert.exists(dObj, DataObject.class);
		return (Group) DomainObjectFactory.newInstance(dObj);
	}
    /** Returns the moderator group. Null if it doesn't exist */
    public Group getModerationGroup() {
        DataObject dObj = (DataObject) get( MODERATION_GROUP );
        Assert.exists(dObj, DataObject.class);
        return (Group)DomainObjectFactory.newInstance(dObj);
    }

	/** Returns the thread create group. Null if it doesn't exist */
	public Group getThreadCreateGroup() {
		DataObject dObj = (DataObject) get(THREAD_CREATE_GROUP);
		Assert.exists(dObj, DataObject.class);
		return (Group) DomainObjectFactory.newInstance(dObj);
	}

	/** Returns the thread reply group. Null if it doesn't exist */
	public Group getThreadResponderGroup() {
		DataObject dObj = (DataObject) get(THREAD_RESPONDER_GROUP);
		Assert.exists(dObj, DataObject.class);
		return (Group) DomainObjectFactory.newInstance(dObj);
	}

	/** Returns the read group. Null if it doesn't exist */
	public Group getReadGroup() {
		DataObject dObj = (DataObject) get(READ_GROUP);
		Assert.exists(dObj, DataObject.class);
		return (Group) DomainObjectFactory.newInstance(dObj);
	}

    public String getContextPath() {
        return "/ccm-forum";
    }

    public String getServletPath() {
        return "/main";
    }

    public void setExpireAfter(int value) {
        set(EXPIRE_AFTER, new BigDecimal(value));
        // remove any previous lifecycle definition
        LifecycleDefinition previousLife = getLifecycleDefinition();
        if (previousLife != null) {
            setLifecycleDefinition(null);
            previousLife.delete();
        }
        if (value == 0) {
            return;
        }
        LifecycleDefinition newLife = new LifecycleDefinition();
        newLife.setLabel("Delete expired noticeboard postings");
		newLife.addPhaseDefinition("Forum posting lifespan",
                                   null,
                                   new Integer(0),
                                   new Integer(1440 * value),  // in minutes
                                   null);
        setLifecycleDefinition(newLife);
        //   We must make sure that all existing postings in this forum
        // have the same expiration policy.
        DataAssociationCursor posts = getPosts().cursor();
        while (posts.next()) {
			Post post =
				(Post) DomainObjectFactory.newInstance(posts.getDataObject());
			if (post
				.getThread()
				.getRootMessage()
				.getID()
				.equals(post.getID())) {

				s_log.debug(
					"Resetting expiration lifecycle for " + post.getOID());
            post.setLifecycle(newLife);
        }
    }
	}

    public int getExpireAfter() {
        BigDecimal expire = (BigDecimal) get(EXPIRE_AFTER);
        if (expire == null) {
            return 0;
        }
        return expire.intValue();
    }

    public LifecycleDefinition getLifecycleDefinition() {
        DataObject life = (DataObject) get(LIFECYCLE_DEFINITION);
        if (life == null) {
            return null;
        }
        return new LifecycleDefinition(life);
    }

    // Not publicly accessible, since setExpireAfter() is frontend
    private void setLifecycleDefinition(LifecycleDefinition life) {
        set(LIFECYCLE_DEFINITION, life);
    }

	/**
	 * method required for upgrade - normally groups are set during forum creation and so 
	 * there is no need to invoke a setter
	 * @author cgyg9330
	 *
	 */
	public void setAdminGroup(Group group) {
		setAssociation(ADMIN_GROUP, group);
		PermissionService.grantPermission(
			new PermissionDescriptor(PrivilegeDescriptor.ADMIN, this, group));
	}
	/**
	 * method required for upgrade - normally groups are set during forum creation and so 
	 * there is no need to invoke a setter
	 * @author cgyg9330
	 *
	 */
	public void setThreadCreatorGroup(Group group) {
		setAssociation(THREAD_CREATE_GROUP, group);
		PermissionService.grantPermission(
			new PermissionDescriptor(
				PrivilegeDescriptor.get(CREATE_THREAD_PRIVILEGE),
				this,
				group));
	}
	/**
	 * method required for upgrade - normally groups are set during forum creation and so 
	 * there is no need to invoke a setter
	 * @author cgyg9330
	 *
	 */
	public void setThreadResponderGroup(Group group) {
		setAssociation(THREAD_RESPONDER_GROUP, group);
		PermissionService.grantPermission(
			new PermissionDescriptor(
				PrivilegeDescriptor.get(RESPOND_TO_THREAD_PRIVILEGE),
				this,
				group));
	}
	/**
	 * method required for upgrade - normally groups are set during forum creation and so 
	 * there is no need to invoke a setter
	 * @author cgyg9330
	 *
	 */
	public void setReaderGroup(Group group) {
		setAssociation(READ_GROUP, group);
		PermissionService.grantPermission(
			new PermissionDescriptor(PrivilegeDescriptor.READ, this, group));
	}

	/**
	 * @return
	 */
	public boolean allowFileAttachments() {
		return ((Boolean) get(ALLOW_FILE_ATTACHMENTS)).booleanValue();
	}
	public boolean allowImageUploads() {
		return ((Boolean) get(ALLOW_IMAGE_UPLOADS)).booleanValue();
	}

	public boolean autoSubscribeThreadStarter() {
		return ((Boolean) get(AUTOSUBSCRIBE_THREAD_STARTER)).booleanValue();
	}

	public boolean noCategoryPostsAllowed() {
		return ((Boolean) get(NO_CATEGORY_POSTS)).booleanValue();
	}
	public boolean anonymousPostsAllowed() {
			return ((Boolean) get(ANONYMOUS_POSTS)).booleanValue();
		}

	public void setAllowFileAttachments(boolean allow) {
		set(ALLOW_FILE_ATTACHMENTS, new Boolean(allow));
	}
	public void setAllowImageUploads(boolean allow) {
		set(ALLOW_IMAGE_UPLOADS, new Boolean(allow));
	}
	public void setAutoSubscribeThreadCreator(boolean subscribe) {
		set(AUTOSUBSCRIBE_THREAD_STARTER, new Boolean(subscribe));
	}

	public void setNoCategoryPostsAllowed(boolean allow) {
		set(NO_CATEGORY_POSTS, new Boolean(allow));
	}
	
	public void setAnonymousPostsAllowed(boolean allow) {
			set(ANONYMOUS_POSTS, new Boolean(allow));
		}

	public void setTitle (String title) {
		String oldTitle = getTitle();
		super.setTitle(title);
		if (!oldTitle.equals(title)) {
			// 1. rename permission groups
			getAdminGroup().setName(title + " Administrators");
			getModerationGroup().setName(title + " Moderators");
			getThreadCreateGroup().setName(title + " Thread Creators");
			getThreadResponderGroup().setName(title + " Thread Responders");
			getReadGroup().setName(title + " Readers");
			DataCollection subscriptions = getSubscriptions();
			while (subscriptions.next()) {
				ForumSubscription subscription = (ForumSubscription)DomainObjectFactory.newInstance(subscriptions.getDataObject());
				subscription.getGroup().setName(subscription.getGroupName(this));
			}
			ThreadCollection threads = getThreads();
			while (threads.next()) {
				ThreadSubscription threadSub = ThreadSubscription.getThreadSubscription(threads.getMessageThread());
				threadSub.getGroup().setName(threadSub.getSubscriptionGroupName(this));
				
			}
						
		}

}
}
