/**
* OLAT - Online Learning and Training<br>
* http://www.olat.org
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br>
* you may not use this file except in compliance with the License.<br>
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing,<br>
* software distributed under the License is distributed on an "AS IS" BASIS, <br>
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
* See the License for the specific language governing permissions and <br>
* limitations under the License.
* <p>
* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br>
* University of Zurich, Switzerland.
* <hr>
* <a href="http://www.openolat.org">
* OpenOLAT - Online Learning and Training</a><br>
* This file has been modified by the OpenOLAT community. Changes are licensed
* under the Apache 2.0 license as the original file.
*/
package org.olat.collaboration;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Hashtable;
import java.util.List;
import org.olat.admin.quota.QuotaConstants;
import org.olat.basesecurity.BaseSecurityManager;
import org.olat.basesecurity.Constants;
import org.olat.basesecurity.GroupRoles;
import org.olat.commons.calendar.CalendarManager;
import org.olat.commons.calendar.ui.CalendarController;
import org.olat.commons.calendar.ui.WeeklyCalendarController;
import org.olat.commons.calendar.ui.components.KalendarRenderWrapper;
import org.olat.core.CoreSpringFactory;
import org.olat.core.commons.modules.bc.FolderRunController;
import org.olat.core.commons.modules.bc.vfs.OlatNamedContainerImpl;
import org.olat.core.commons.modules.bc.vfs.OlatRootFolderImpl;
import org.olat.core.commons.services.notifications.SubscriptionContext;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.components.stack.TooledStackedPanel;
import org.olat.core.gui.control.Controller;
import org.olat.core.gui.control.WindowControl;
import org.olat.core.gui.control.generic.title.TitleInfo;
import org.olat.core.gui.translator.Translator;
import org.olat.core.id.Identity;
import org.olat.core.id.OLATResourceable;
import org.olat.core.id.context.BusinessControl;
import org.olat.core.id.context.BusinessControlFactory;
import org.olat.core.id.context.ContextEntry;
import org.olat.core.logging.AssertException;
import org.olat.core.logging.OLog;
import org.olat.core.logging.Tracing;
import org.olat.core.logging.activity.ThreadLocalUserActivityLogger;
import org.olat.core.util.FileUtils;
import org.olat.core.util.Util;
import org.olat.core.util.ZipUtil;
import org.olat.core.util.coordinate.CoordinatorManager;
import org.olat.core.util.coordinate.SyncerCallback;
import org.olat.core.util.coordinate.SyncerExecutor;
import org.olat.core.util.i18n.I18nModule;
import org.olat.core.util.mail.ContactMessage;
import org.olat.core.util.vfs.LocalFolderImpl;
import org.olat.core.util.vfs.Quota;
import org.olat.core.util.vfs.QuotaManager;
import org.olat.core.util.vfs.VFSContainer;
import org.olat.core.util.vfs.VFSLeaf;
import org.olat.core.util.vfs.callbacks.VFSSecurityCallback;
import org.olat.course.CourseFactory;
import org.olat.course.CourseModule;
import org.olat.course.ICourse;
import org.olat.course.run.calendar.CourseLinkProviderController;
import org.olat.group.BusinessGroup;
import org.olat.group.BusinessGroupService;
import org.olat.group.ui.run.InfoGroupRunController;
import org.olat.instantMessaging.InstantMessagingModule;
import org.olat.instantMessaging.ui.ChatToolController;
import org.olat.modules.co.ContactFormController;
import org.olat.modules.fo.Forum;
import org.olat.modules.fo.ForumCallback;
import org.olat.modules.fo.ForumUIFactory;
import org.olat.modules.fo.archiver.ForumArchiveManager;
import org.olat.modules.fo.archiver.formatters.ForumFormatter;
import org.olat.modules.fo.archiver.formatters.ForumRTFFormatter;
import org.olat.modules.fo.manager.ForumManager;
import org.olat.modules.openmeetings.OpenMeetingsModule;
import org.olat.modules.openmeetings.manager.OpenMeetingsException;
import org.olat.modules.openmeetings.manager.OpenMeetingsManager;
import org.olat.modules.openmeetings.model.OpenMeetingsRoom;
import org.olat.modules.openmeetings.model.RoomType;
import org.olat.modules.openmeetings.ui.OpenMeetingsRunController;
import org.olat.modules.portfolio.Binder;
import org.olat.modules.portfolio.BinderConfiguration;
import org.olat.modules.portfolio.BinderSecurityCallback;
import org.olat.modules.portfolio.BinderSecurityCallbackFactory;
import org.olat.modules.portfolio.PortfolioLoggingAction;
import org.olat.modules.portfolio.PortfolioService;
import org.olat.modules.portfolio.PortfolioV2Module;
import org.olat.modules.portfolio.manager.BinderUserInformationsDAO;
import org.olat.modules.portfolio.ui.BinderController;
import org.olat.modules.wiki.WikiManager;
import org.olat.modules.wiki.WikiSecurityCallback;
import org.olat.modules.wiki.WikiSecurityCallbackImpl;
import org.olat.modules.wiki.WikiToZipUtils;
import org.olat.portfolio.EPSecurityCallback;
import org.olat.portfolio.EPSecurityCallbackImpl;
import org.olat.portfolio.EPUIFactory;
import org.olat.portfolio.manager.EPFrontendManager;
import org.olat.portfolio.model.structel.PortfolioStructureMap;
import org.olat.portfolio.ui.structel.EPCreateMapController;
import org.olat.properties.NarrowedPropertyManager;
import org.olat.properties.Property;
import org.olat.properties.PropertyManager;
import org.olat.repository.RepositoryEntry;
import org.olat.util.logging.activity.LoggingResourceable;
/**
* Description:<BR>
* The singleton used for retrieving a collaboration tools suite associated with
* the supplied OLATResourceable.
* <P>
* Description: <br>
* The CollaborationTools represents a suite of collaborative tools addeable to
* any OLATResourceable. To get an instance of this suite, one has to use the
* collaboration tools factory.
* <p>
* This collaboration tools class exposes the possibility to retrieve the
* appropriate controllers for the desired tools. And also provides the means to
* manage the configuration of the provided tools. Moreover it is already
* shipped with a controller which can be used to display an administrative view
* for enabling/disabling such tools for the supplied OLATResourceable.
* <p>
* All the future collaborative tools will be found here.
*
* @see org.olat.collaboration.CollaborationToolsFactory
* @author Felix Jost
* @author guido
*/
public class CollaborationTools implements Serializable {
private static final long serialVersionUID = -155629068939748789L;
boolean dirty = false;
private final static String TRUE = "true";
private final static String FALSE = "false";
public final static String KEY_FORUM = "forumKey";
public final static String KEY_PORTFOLIO = "portfolioMapKey";
public final static String KEY_OPENMEETINGS = "openMeetingsKey";
/**
* <code>PROP_CAT_BG_COLLABTOOLS</code> identifies properties concerning
* Collaboration Tools
*/
public final static String PROP_CAT_BG_COLLABTOOLS = "collabtools";
/**
* constant used to identify the calendar for a BuddyGroup
*/
public final static String TOOL_CALENDAR = "hasCalendar";
/**
* constant used to identify the forum for a BuddyGroup
*/
public final static String TOOL_FORUM = "hasForum";
/**
* constant used to identify the folder for a BuddyGroup
*/
public final static String TOOL_FOLDER = "hasFolder";
/**
* constant used to identify the chat for a BuddyGroup
*/
public final static String TOOL_CHAT = "hasChat";
/**
* constant used to identify the contact form for a BuddyGroup
*/
public final static String TOOL_CONTACT = "hasContactForm";
/**
* constant used to identify the contact form for a BuddyGroup
*/
public final static String TOOL_NEWS = "hasNews";
/**
* constant used to identify the wiki for a BuddyGroup
*/
public final static String TOOL_WIKI = "hasWiki";
/**
* constant used to identify the portfolio for a BuddyGroup
*/
public final static String TOOL_PORTFOLIO = "hasPortfolio";
/**
* constant used to identify the open meetings for a group
*/
public final static String TOOL_OPENMEETINGS = "hasOpenMeetings";
/**
* Only owners have write access to the calendar.
*/
public static final int CALENDAR_ACCESS_OWNERS = 0;
/**
* Owners and members have write access to the calendar.
*/
public static final int CALENDAR_ACCESS_ALL = 1;
/**
* Only owners have write access to the folder.
*/
public static final int FOLDER_ACCESS_OWNERS = 0;
/**
* Owners and members have write access to the folder.
*/
public static final int FOLDER_ACCESS_ALL = 1;
/**
* cache for Boolean Objects representing the State
*/
private final static String KEY_NEWS = "news";
private final static String KEY_NEWS_ACCESS = "newsAccess";
public final static String KEY_CALENDAR_ACCESS = "cal";
public final static String KEY_FOLDER_ACCESS = "folder";
//o_clusterOK by guido
private Hashtable<String, Boolean> cacheToolStates;
private final BusinessGroup ores;
private static OLog log = Tracing.createLoggerFor(CollaborationTools.class);
private transient CoordinatorManager coordinatorManager;
/**
* package local constructor only
*
* @param ores
*/
CollaborationTools(CoordinatorManager coordinatorManager, BusinessGroup ores) {
this.coordinatorManager = coordinatorManager;
this.ores = ores;
this.cacheToolStates = new Hashtable<String, Boolean>();
}
/**
* @param ureq
* @return a news controller
*/
public Controller createNewsController(UserRequest ureq, WindowControl wControl) {
String news = lookupNews();
return new SimpleNewsController(ureq, wControl, news);
}
public Controller createInfoMessageController(UserRequest ureq, WindowControl wControl, boolean isAdmin) {
String accessProperty = getNewsAccessProperty();
boolean canAccess = "all".equals(accessProperty);
return new InfoGroupRunController(ureq, wControl, ores, canAccess, isAdmin);
}
/**
* TODO: rename to getForumController and save instance?
*
* @param ureq
* @param wControl
* @param isAdmin
* @param subsContext the subscriptioncontext if subscriptions to this forum
* should be possible
* @return a forum controller
*/
public Controller createForumController(UserRequest ureq, WindowControl wControl, boolean isAdmin, boolean isGuestOnly,
final SubscriptionContext subsContext) {
final boolean isAdm = isAdmin;
final boolean isGuest = isGuestOnly;
Forum forum = getForum();
Translator trans = Util.createPackageTranslator(this.getClass(), ureq.getLocale());
TitleInfo titleInfo = new TitleInfo(null, trans.translate("collabtools.named.hasForum"));
titleInfo.setSeparatorEnabled(true);
Controller forumController = ForumUIFactory.getTitledForumController(ureq, wControl, forum, new ForumCallback() {
@Override
public boolean mayUsePseudonym() {
return false;
}
@Override
public boolean mayOpenNewThread() {
return true;
}
@Override
public boolean mayReplyMessage() {
return true;
}
@Override
public boolean mayEditOwnMessage() {
return true;
}
@Override
public boolean mayDeleteOwnMessage() {
return true;
}
@Override
public boolean mayEditMessageAsModerator() {
return isAdm;
}
@Override
public boolean mayDeleteMessageAsModerator() {
return isAdm;
}
@Override
public boolean mayArchiveForum() {
return !isGuest;
}
@Override
public boolean mayFilterForUser() {
return isAdm;
}
@Override
public SubscriptionContext getSubscriptionContext() {
return subsContext;
}
}, titleInfo);
return forumController;
}
public Forum getForum() {
final ForumManager fom = ForumManager.getInstance();
final NarrowedPropertyManager npm = NarrowedPropertyManager.getInstance(ores);
Property forumProperty = npm.findProperty(null, null, PROP_CAT_BG_COLLABTOOLS, KEY_FORUM);
Forum forum;
if(forumProperty != null) {
forum = fom.loadForum(forumProperty.getLongValue());
} else {
//TODO gsync
forum = coordinatorManager.getCoordinator().getSyncer().doInSync(ores, new SyncerCallback<Forum>(){
public Forum execute() {
Forum aforum;
Long forumKey;
Property forumKeyProperty = npm.findProperty(null, null, PROP_CAT_BG_COLLABTOOLS, KEY_FORUM);
if (forumKeyProperty == null) {
// First call of forum, create new forum and save
aforum = fom.addAForum();
forumKey = aforum.getKey();
if (log.isDebug()) {
log.debug("created new forum in collab tools: foid::" + forumKey.longValue() + " for ores::"
+ ores.getResourceableTypeName() + "/" + ores.getResourceableId());
}
forumKeyProperty = npm.createPropertyInstance(null, null, PROP_CAT_BG_COLLABTOOLS, KEY_FORUM, null, forumKey, null, null);
npm.saveProperty(forumKeyProperty);
} else {
// Forum does already exist, load forum with key from properties
forumKey = forumKeyProperty.getLongValue();
aforum = fom.loadForum(forumKey);
if (aforum == null) { throw new AssertException("Unable to load forum with key " + forumKey.longValue() + " for ores "
+ ores.getResourceableTypeName() + " with key " + ores.getResourceableId()); }
if (log.isDebug()) {
log.debug("loading forum in collab tools from properties: foid::" + forumKey.longValue() + " for ores::"
+ ores.getResourceableTypeName() + "/" + ores.getResourceableId());
}
}
return aforum;
}
});
}
return forum;
}
public String getFolderRelPath() {
return "/cts/folders/" + ores.getResourceableTypeName() + "/" + ores.getResourceableId();
}
/**
* Creates a folder run controller with all rights enabled for everybody
*
* @param ureq
* @param wControl
* @param subsContext
* @return Copnfigured FolderRunController
*/
public FolderRunController createFolderController(UserRequest ureq, WindowControl wControl,
BusinessGroup businessGroup, boolean isAdmin, final SubscriptionContext subsContext) {
// do not use a global translator since in the fututre a collaborationtools
// may be shared among users
Translator trans = Util.createPackageTranslator(this.getClass(), ureq.getLocale());
OlatRootFolderImpl rootContainer = getSecuredFolder(businessGroup, subsContext, ureq.getIdentity(), isAdmin);
OlatNamedContainerImpl namedContainer = new OlatNamedContainerImpl(trans.translate("folder"), rootContainer);
FolderRunController frc = new FolderRunController(namedContainer, true, true, true, ureq, wControl);
return frc;
}
/**
* Return the root VFS container with security callback set
* @return
*/
public OlatRootFolderImpl getSecuredFolder(BusinessGroup businessGroup, SubscriptionContext subsContext, Identity identity, boolean isAdmin) {
if(!isToolEnabled(CollaborationTools.TOOL_FOLDER)) {
return null;
}
boolean writeAccess;
boolean isOwner = CoreSpringFactory.getImpl(BusinessGroupService.class).hasRoles(identity, businessGroup, GroupRoles.coach.name());
if (!(isAdmin || isOwner)) {
// check if participants have read/write access
int folderAccess = CollaborationTools.FOLDER_ACCESS_ALL;
Long lFolderAccess = CollaborationToolsFactory.getInstance().getOrCreateCollaborationTools(businessGroup).lookupFolderAccess();
if (lFolderAccess != null) {
folderAccess = lFolderAccess.intValue();
}
writeAccess = (folderAccess == CollaborationTools.CALENDAR_ACCESS_ALL);
} else {
writeAccess = true;
}
String relPath = getFolderRelPath();
VFSSecurityCallback secCallback = new CollabSecCallback(writeAccess, relPath, subsContext);
OlatRootFolderImpl rootContainer = new OlatRootFolderImpl(relPath, null);
rootContainer.setLocalSecurityCallback(secCallback);
return rootContainer;
}
/**
* Creates a calendar controller
* @param ureq
* @param wControl
* @param resourceableId
* @return Configured WeeklyCalendarController
*/
public CalendarController createCalendarController(UserRequest ureq, WindowControl wControl, BusinessGroup businessGroup,
boolean isAdmin, boolean isMember) {
CollaborationManager collaborationManager = CoreSpringFactory.getImpl(CollaborationManager.class);
KalendarRenderWrapper calRenderWrapper = collaborationManager.getCalendar(businessGroup, ureq, isAdmin);
calRenderWrapper.setPrivateEventsVisible(isAdmin || isMember);
// add linking
List<RepositoryEntry> repoEntries = CoreSpringFactory.getImpl(BusinessGroupService.class).findRepositoryEntries(Collections.singleton(businessGroup), 0, -1);
List<ICourse> courses = new ArrayList<ICourse>(repoEntries.size());
for (RepositoryEntry repoEntry:repoEntries) {
if (repoEntry.getOlatResource().getResourceableTypeName().equals(CourseModule.getCourseTypeName())) {
ICourse course = CourseFactory.loadCourse(repoEntry);
courses.add(course);
}
}
if(!courses.isEmpty()) {
CourseLinkProviderController clp = new CourseLinkProviderController(null, courses, ureq, wControl);
calRenderWrapper.setLinkProvider(clp);
}
List<KalendarRenderWrapper> calendars = new ArrayList<KalendarRenderWrapper>();
calendars.add(calRenderWrapper);
return new WeeklyCalendarController(ureq, wControl, calendars,
WeeklyCalendarController.CALLER_COLLAB, false);
}
/**
* @param ureq
* @param wControl
* @return a contact form controller
*/
public ContactFormController createContactFormController(UserRequest ureq, WindowControl wControl, ContactMessage cmsg) {
ContactFormController cfc = new ContactFormController(ureq, wControl, true, false, false, cmsg);
return cfc;
}
/**
* @param ureq
* @param wControl
* @param chatName
* @return Controller
*/
public ChatToolController createChatController(UserRequest ureq, WindowControl wControl, BusinessGroup grp, boolean isAdmin) {
InstantMessagingModule imModule = CoreSpringFactory.getImpl(InstantMessagingModule.class);
if (imModule.isEnabled() && imModule.isGroupEnabled()) {
return new ChatToolController(ureq, wControl, grp, isAdmin);
}
return null;
}
/**
* return an controller for the wiki tool
* @param ureq
* @param wControl
* @return
*/
public Controller createWikiController(UserRequest ureq, WindowControl wControl) {
// Check for jumping to certain wiki page
BusinessControl bc = wControl.getBusinessControl();
ContextEntry ce = bc.popLauncherContextEntry();
SubscriptionContext subContext = new SubscriptionContext(ores, WikiManager.WIKI_RESOURCE_FOLDER_NAME);
boolean isOlatAdmin = ureq.getUserSession().getRoles().isOLATAdmin();
boolean isGuestOnly = ureq.getUserSession().getRoles().isGuestOnly();
boolean isResourceOwner = BaseSecurityManager.getInstance().isIdentityPermittedOnResourceable(ureq.getIdentity(), Constants.PERMISSION_ACCESS, ores);
WikiSecurityCallback callback = new WikiSecurityCallbackImpl(null, isOlatAdmin, isGuestOnly, true, isResourceOwner, subContext);
if ( ce != null ) { //jump to a certain context
OLATResourceable ceOres = ce.getOLATResourceable();
String typeName = ceOres.getResourceableTypeName();
String page = typeName.substring("page=".length());
if(page != null && page.endsWith(":0")) {
page = page.substring(0, page.length() - 2);
}
return WikiManager.getInstance().createWikiMainController(ureq, wControl, ores, callback, page);
} else {
return WikiManager.getInstance().createWikiMainController(ureq, wControl, ores, callback, null);
}
}
/**
* return an controller for the wiki tool
* @param ureq
* @param wControl
* @return
*/
public Controller createPortfolioController(final UserRequest ureq, final WindowControl wControl,
final TooledStackedPanel stackPanel, final BusinessGroup group) {
final NarrowedPropertyManager npm = NarrowedPropertyManager.getInstance(ores);
Property mapProperty = npm.findProperty(null, null, PROP_CAT_BG_COLLABTOOLS, KEY_PORTFOLIO);
if(mapProperty != null) {
return createPortfolioController(ureq, wControl, stackPanel, mapProperty);
} else {
//TODO gsync
return coordinatorManager.getCoordinator().getSyncer().doInSync(ores, new SyncerCallback<Controller>() {
@Override
public Controller execute() {
Controller ctrl;
Property mapKeyProperty = npm.findProperty(null, null, PROP_CAT_BG_COLLABTOOLS, KEY_PORTFOLIO);
if (mapKeyProperty == null) {
PortfolioV2Module moduleV2 = CoreSpringFactory.getImpl(PortfolioV2Module.class);
if(moduleV2.isEnabled()) {
PortfolioService portfolioService = CoreSpringFactory.getImpl(PortfolioService.class);
Binder binder = portfolioService.createNewBinder(group.getName(), group.getDescription(), null, null);
CoreSpringFactory.getImpl(BinderUserInformationsDAO.class).updateBinderUserInformationsInSync(binder, ureq.getIdentity());
mapKeyProperty = npm.createPropertyInstance(null, null, PROP_CAT_BG_COLLABTOOLS, KEY_PORTFOLIO, null, binder.getKey(), "2", null);
BinderSecurityCallback secCallback = BinderSecurityCallbackFactory.getCallbackForBusinessGroup();
BinderController binderCtrl = new BinderController(ureq, wControl, stackPanel, secCallback, binder, BinderConfiguration.createBusinessGroupConfig());
List<ContextEntry> entries = BusinessControlFactory.getInstance().createCEListFromResourceType("Toc");
binderCtrl.activate(ureq, entries, null);
ctrl = binderCtrl;
ThreadLocalUserActivityLogger.addLoggingResourceInfo(LoggingResourceable.wrap(binder));
ThreadLocalUserActivityLogger.log(PortfolioLoggingAction.PORTFOLIO_BINDER_CREATED, getClass());
} else {
EPFrontendManager ePFMgr = CoreSpringFactory.getImpl(EPFrontendManager.class);
PortfolioStructureMap map = ePFMgr.createAndPersistPortfolioDefaultMap(group.getName(), group.getDescription());
Translator pT = Util.createPackageTranslator(EPCreateMapController.class, ureq.getLocale());
// add a page, as each map should have at least one per default!
ePFMgr.createAndPersistPortfolioPage(map, pT.translate("new.page.title"), pT.translate("new.page.desc"));
mapKeyProperty = npm.createPropertyInstance(null, null, PROP_CAT_BG_COLLABTOOLS, KEY_PORTFOLIO, null, map.getKey(), null, null);
EPSecurityCallback secCallback = new EPSecurityCallbackImpl(true, true);
ctrl = EPUIFactory.createMapViewController(ureq, wControl, map, secCallback);
}
npm.saveProperty(mapKeyProperty);
} else {
ctrl = createPortfolioController(ureq, wControl, stackPanel, mapProperty);
}
return ctrl;
}
});
}
}
/**
*
* @param ureq
* @param wControl
* @param mapProperty The property is mandatory!
* @return
*/
private Controller createPortfolioController(UserRequest ureq, WindowControl wControl, TooledStackedPanel stackPanel,
Property mapProperty) {
Long key = mapProperty.getLongValue();
String version = mapProperty.getStringValue();
Controller ctrl;
if("2".equals(version)) {
PortfolioService portfolioService = CoreSpringFactory.getImpl(PortfolioService.class);
Binder binder = portfolioService.getBinderByKey(key);
portfolioService.updateBinderUserInformations(binder, ureq.getIdentity());
BinderSecurityCallback secCallback = BinderSecurityCallbackFactory.getCallbackForBusinessGroup();
BinderController binderCtrl = new BinderController(ureq, wControl, stackPanel, secCallback, binder, BinderConfiguration.createBusinessGroupConfig());
ctrl = binderCtrl;
} else {
PortfolioStructureMap map = (PortfolioStructureMap) CoreSpringFactory.getImpl(EPFrontendManager.class)
.loadPortfolioStructureByKey(key);
EPSecurityCallback secCallback = new EPSecurityCallbackImpl(true, true);
ctrl = EPUIFactory.createMapViewController(ureq, wControl, map, secCallback);
}
return ctrl;
}
public Controller createOpenMeetingsController(final UserRequest ureq, WindowControl wControl, final BusinessGroup group, boolean admin) {
OpenMeetingsRunController runController = new OpenMeetingsRunController(ureq, wControl, group, null, null, admin, admin, false);
return runController;
}
/**
* @param toolToChange
* @param enable
*/
public void setToolEnabled(String toolToChange, boolean enable) {
createOrUpdateProperty(toolToChange, enable);
}
/**
* reads from the internal cache. <b>Precondition </b> cache must be filled at
* CollaborationTools creation time.
*
* @param enabledTool
* @return boolean
*/
public boolean isToolEnabled(String enabledTool) {
//o_clusterOK as whole object gets invalidated if tool is added or deleted
if (!cacheToolStates.containsKey(enabledTool)) {
// not in cache yet, read property first (see getPropertyOf(..))
getPropertyOf(enabledTool);
}
// POSTCONDITION: cacheToolStates.get(enabledTool) != null
Boolean cachedValue = cacheToolStates.get(enabledTool);
return cachedValue.booleanValue();
}
/**
* delete all CollaborationTools stuff from the database, which is related to
* the calling OLATResourceable.
*/
public void deleteTools(BusinessGroup businessGroupTodelete) {
NarrowedPropertyManager npm = NarrowedPropertyManager.getInstance(ores);
/*
* delete the forum, if existing
*/
ForumManager fom = ForumManager.getInstance();
Property forumKeyProperty = npm.findProperty(null, null, PROP_CAT_BG_COLLABTOOLS, KEY_FORUM);
if (forumKeyProperty != null) {
// if there was a forum, delete it
Long forumKey = forumKeyProperty.getLongValue();
if (forumKey == null) throw new AssertException("property had no longValue, prop:" + forumKeyProperty);
fom.deleteForum(forumKey);
}
/*
* delete the folder, if existing
*/
OlatRootFolderImpl vfsContainer = new OlatRootFolderImpl(getFolderRelPath(), null);
File fFolderRoot = vfsContainer.getBasefile();
if (fFolderRoot.exists()) {
FileUtils.deleteDirsAndFiles(fFolderRoot, true, true);
}
/*
* delete the wiki if existing
*/
VFSContainer rootContainer = WikiManager.getInstance().getWikiRootContainer(ores);
if(rootContainer != null) rootContainer.delete();
/*
* Delete calendar if exists
*/
if (businessGroupTodelete != null) {
CalendarManager calManager = CoreSpringFactory.getImpl(CalendarManager.class);
calManager.deleteGroupCalendar(businessGroupTodelete);
}
/*
* delete chatRoom
*/
// no cleanup needed, automatically done when last user exits the room
/*
* delete all Properties defining enabled/disabled CollabTool XY and the
* news content
*/
npm.deleteProperties(null, null, PROP_CAT_BG_COLLABTOOLS, null);
/*
* Delete OpenMeetings room
*/
OpenMeetingsModule omModule = CoreSpringFactory.getImpl(OpenMeetingsModule.class);
if(omModule.isEnabled()) {
OpenMeetingsManager omManager = CoreSpringFactory.getImpl(OpenMeetingsManager.class);
try {
omManager.deleteAll(ores, null, null);
} catch (OpenMeetingsException e) {
log.error("A room could not be deleted for group: " + ores, e);
}
}
/*
* and last but not least the cache is reseted
*/
cacheToolStates.clear();
this.dirty = true;
}
private void openOpenMeetingsRoom() {
OpenMeetingsModule omModule = CoreSpringFactory.getImpl(OpenMeetingsModule.class);
if(!omModule.isEnabled()) return;
OpenMeetingsManager omm = CoreSpringFactory.getImpl(OpenMeetingsManager.class);
Long roomId = omm.getRoomId(ores, null, null);
if(roomId == null) {
//create the room
OpenMeetingsRoom room = new OpenMeetingsRoom();
room.setComment(ores.getDescription());
room.setModerated(true);
room.setName(ores.getName());
room.setAudioOnly(true);
room.setResourceName(ores.getName());
room.setSize(25);
room.setType(RoomType.conference.type());
omm.addRoom(ores, null, null, room);
}
}
/**
* creates the property if non-existing, or updates the existing property to
* the supplied values. Real changes are made persistent immediately.
*
* @param selectedTool
* @param toolValue
*/
private void createOrUpdateProperty(final String selectedTool, final boolean toolValue) {
Boolean cv = cacheToolStates.get(selectedTool);
if (cv != null && cv.booleanValue() == toolValue) {
return; // nice, cache saved a needless update
}
// handle Boolean Values via String Field in Property DB Table
final String toolValueStr = toolValue ? TRUE : FALSE;
final PropertyManager pm = PropertyManager.getInstance();
//TODO gsync
coordinatorManager.getCoordinator().getSyncer().doInSync(ores, new SyncerExecutor() {
public void execute() {
//was: synchronized (CollaborationTools.class) {
Property property = getPropertyOf(selectedTool);
if (property == null) {
// not existing -> create it
property = pm.createPropertyInstance(null, null, ores, PROP_CAT_BG_COLLABTOOLS, selectedTool, null, null, toolValueStr, null);
} else {
// if existing -> update to desired value
property.setStringValue(toolValueStr);
}
//create a room if needed
if(toolValue && TOOL_OPENMEETINGS.equals(selectedTool)) {
openOpenMeetingsRoom();
}
// property becomes persistent
pm.saveProperty(property);
}});
this.dirty = true;
cacheToolStates.put(selectedTool, Boolean.valueOf(toolValue));
}
Property getPropertyOf(String selectedTool) {
PropertyManager pm = PropertyManager.getInstance();
Property property = pm.findProperty(null, null, ores, PROP_CAT_BG_COLLABTOOLS, selectedTool);
Boolean res;
if (property == null) { // meaning false
res = Boolean.FALSE;
} else {
String val = property.getStringValue();
res = val.equals(TRUE) ? Boolean.TRUE : Boolean.FALSE;
}
cacheToolStates.put(selectedTool, res);
return property;
}
/**
* create the Collaboration Tools Suite. This Controller handles the
* enabling/disabling of Collab Tools.
*
* @param ureq
* @return a collaboration tools settings controller
*/
public CollaborationToolsSettingsController createCollaborationToolsSettingsController(UserRequest ureq, WindowControl wControl) {
return new CollaborationToolsSettingsController(ureq, wControl, ores);
}
/**
* @return Gets the news access property
*/
public String getNewsAccessProperty() {
NarrowedPropertyManager npm = NarrowedPropertyManager.getInstance(ores);
Property property = npm.findProperty(null, null, PROP_CAT_BG_COLLABTOOLS, KEY_NEWS_ACCESS);
if (property == null) { // no entry
return null;
}
// read the text value of the existing property
String text = property.getStringValue();
return text;
}
/**
* @param Save news access property.
*/
public void saveNewsAccessProperty(String access) {
NarrowedPropertyManager npm = NarrowedPropertyManager.getInstance(ores);
Property property = npm.findProperty(null, null, PROP_CAT_BG_COLLABTOOLS, KEY_NEWS_ACCESS);
if (property == null) { // create a new one
Property nP = npm.createPropertyInstance(null, null, PROP_CAT_BG_COLLABTOOLS, KEY_NEWS_ACCESS, null, null, access, null);
npm.saveProperty(nP);
} else { // modify the existing one
property.setStringValue(access);
npm.updateProperty(property);
}
}
/**
* @return the news; if there is no news yet: return null;
*/
public String lookupNews() {
NarrowedPropertyManager npm = NarrowedPropertyManager.getInstance(ores);
Property property = npm.findProperty(null, null, PROP_CAT_BG_COLLABTOOLS, KEY_NEWS);
if (property == null) { // no entry
return null;
}
// read the text value of the existing property
String text = property.getTextValue();
return text;
}
public Property lookupNewsDBEntry() {
NarrowedPropertyManager npm = NarrowedPropertyManager.getInstance(ores);
Property property = npm.findProperty(null, null, PROP_CAT_BG_COLLABTOOLS, KEY_NEWS);
if (property == null) { // no entry
return null;
} else return property;
}
/**
* @param news
*/
public void saveNews(String news) {
NarrowedPropertyManager npm = NarrowedPropertyManager.getInstance(ores);
Property property = npm.findProperty(null, null, PROP_CAT_BG_COLLABTOOLS, KEY_NEWS);
if (property == null) { // create a new one
Property nP = npm.createPropertyInstance(null, null, PROP_CAT_BG_COLLABTOOLS, KEY_NEWS, null, null, null, news);
npm.saveProperty(nP);
} else { // modify the existing one
property.setTextValue(news);
npm.updateProperty(property);
}
}
public void setToolAccess(String tool, Integer access) {
if (TOOL_FOLDER.equals(tool) && access != null) {
if (FOLDER_ACCESS_ALL == access.intValue()) {
saveFolderAccess(new Long(FOLDER_ACCESS_ALL));
} else if (FOLDER_ACCESS_OWNERS == access.intValue()) {
saveFolderAccess(new Long(FOLDER_ACCESS_OWNERS));
}
} else if (TOOL_CALENDAR.equals(tool) && access != null) {
if (CALENDAR_ACCESS_ALL == access.intValue()) {
saveCalendarAccess(new Long(CALENDAR_ACCESS_ALL));
} else if (CALENDAR_ACCESS_OWNERS == access.intValue()) {
saveCalendarAccess(new Long(CALENDAR_ACCESS_OWNERS));
}
}
}
public Long lookupCalendarAccess() {
NarrowedPropertyManager npm = NarrowedPropertyManager.getInstance(ores);
Property property = npm.findProperty(null, null, PROP_CAT_BG_COLLABTOOLS, KEY_CALENDAR_ACCESS);
if (property == null) { // no entry
return null;
}
// read the long value of the existing property
return property.getLongValue();
}
public void saveCalendarAccess(Long calendarAccess) {
NarrowedPropertyManager npm = NarrowedPropertyManager.getInstance(ores);
Property property = npm.findProperty(null, null, PROP_CAT_BG_COLLABTOOLS, KEY_CALENDAR_ACCESS);
if (property == null) { // create a new one
Property nP = npm.createPropertyInstance(null, null, PROP_CAT_BG_COLLABTOOLS, KEY_CALENDAR_ACCESS, null, calendarAccess, null, null);
npm.saveProperty(nP);
} else { // modify the existing one
property.setLongValue(calendarAccess);
npm.updateProperty(property);
}
}
public Long lookupFolderAccess() {
NarrowedPropertyManager npm = NarrowedPropertyManager.getInstance(ores);
Property property = npm.findProperty(null, null, PROP_CAT_BG_COLLABTOOLS, KEY_FOLDER_ACCESS);
if (property == null) { // no entry
return null;
}
// read the long value of the existing property
return property.getLongValue();
}
public void saveFolderAccess(Long folderrAccess) {
NarrowedPropertyManager npm = NarrowedPropertyManager.getInstance(ores);
Property property = npm.findProperty(null, null, PROP_CAT_BG_COLLABTOOLS, KEY_FOLDER_ACCESS);
if (property == null) { // create a new one
Property nP = npm.createPropertyInstance(null, null, PROP_CAT_BG_COLLABTOOLS, KEY_FOLDER_ACCESS, null, folderrAccess, null, null);
npm.saveProperty(nP);
} else { // modify the existing one
property.setLongValue(folderrAccess);
npm.updateProperty(property);
}
}
public class CollabSecCallback implements VFSSecurityCallback {
private final boolean write;
private Quota folderQuota = null;
private SubscriptionContext subsContext;
public CollabSecCallback(boolean write, String relPath, SubscriptionContext subsContext) {
this.subsContext = subsContext;
initFolderQuota(relPath);
this.write = write;
}
private void initFolderQuota(String relPath) {
QuotaManager qm = QuotaManager.getInstance();
folderQuota = qm.getCustomQuota(relPath);
if (folderQuota == null) {
Quota defQuota = qm.getDefaultQuota(QuotaConstants.IDENTIFIER_DEFAULT_GROUPS);
folderQuota = QuotaManager.getInstance().createQuota(relPath, defQuota.getQuotaKB(), defQuota.getUlLimitKB());
}
}
public boolean canRead() {
return true;
}
public boolean canWrite() {
return write;
}
@Override
public boolean canCreateFolder() {
return write;
}
public boolean canDelete() {
return write;
}
public boolean canList() {
return true;
}
public boolean canCopy() {
return true;
}
public boolean canDeleteRevisionsPermanently() {
return write;
}
public Quota getQuota() {
return folderQuota;
}
public void setQuota(Quota quota) {
this.folderQuota = quota;
}
public SubscriptionContext getSubscriptionContext() {
return subsContext;
}
}
/**
* It is assumed that this is only called by an administrator
* (e.g. at deleteGroup)
* @param archivFilePath
*/
public void archive(String archivFilePath) {
if (isToolEnabled(CollaborationTools.TOOL_FORUM)) {
archiveForum(archivFilePath);
}
if (isToolEnabled(CollaborationTools.TOOL_WIKI)) {
archiveWiki(archivFilePath);
}
if (isToolEnabled(CollaborationTools.TOOL_FOLDER)) {
archiveFolder(archivFilePath);
}
}
private void archiveForum(String archivFilePath) {
Property forumKeyProperty = NarrowedPropertyManager.getInstance(ores).findProperty(null, null, PROP_CAT_BG_COLLABTOOLS, KEY_FORUM);
if (forumKeyProperty != null) {
VFSContainer archiveContainer = new LocalFolderImpl(new File(archivFilePath));
String archiveForumName = "del_forum_" + forumKeyProperty.getLongValue();
VFSContainer archiveForumContainer = archiveContainer.createChildContainer(archiveForumName);
ForumFormatter ff = new ForumRTFFormatter(archiveForumContainer, false, I18nModule.getDefaultLocale());
ForumArchiveManager.getInstance().applyFormatter(ff, forumKeyProperty.getLongValue(), null);
}
}
private void archiveWiki(String archivFilePath) {
VFSContainer wikiContainer = WikiManager.getInstance().getWikiRootContainer(ores);
VFSLeaf wikiZip = WikiToZipUtils.getWikiAsZip(wikiContainer);
String exportFileName = "del_wiki_" + ores.getResourceableId() + ".zip";
File archiveDir = new File(archivFilePath);
if (!archiveDir.exists()) {
archiveDir.mkdir();
}
String fullFilePath = archivFilePath + File.separator + exportFileName;
try {
FileUtils.bcopy(wikiZip.getInputStream(), new File(fullFilePath), "archive wiki");
} catch (FileNotFoundException e) {
log.warn("Can not archive wiki repoEntry=" + ores.getResourceableId());
} catch (IOException ioe) {
log.warn("Can not archive wiki repoEntry=" + ores.getResourceableId());
}
}
private void archiveFolder(String archiveFilePath) {
OlatRootFolderImpl folderContainer = new OlatRootFolderImpl(getFolderRelPath(), null);
File fFolderRoot = folderContainer.getBasefile();
if (fFolderRoot.exists()) {
String zipFileName = "del_folder_" + ores.getResourceableId() + ".zip";
String fullZipFilePath = archiveFilePath + File.separator + zipFileName;
ZipUtil.zipAll(fFolderRoot, new File(fullZipFilePath), true);
}
}
/**
* whole object gets cached, if tool gets added or deleted the object becomes dirty and will be removed from cache.
* @return
*/
protected boolean isDirty() {
return dirty;
}
}