/* * 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; } /** * Experimental * 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)); } } } }