Newer
Older
/**
* <a href="http://www.openolat.org">
* OpenOLAT - Online Learning and Training</a><br>
* <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 the
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
* <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>
* Initial code contributed and copyrighted by<br>
* frentix GmbH, http://www.frentix.com
* <p>
*/
package org.olat.course;
import org.olat.admin.quota.QuotaConstants;
import org.olat.core.CoreSpringFactory;
import org.olat.core.commons.modules.bc.vfs.OlatRootFolderImpl;
import org.olat.core.commons.services.notifications.SubscriptionContext;
import org.olat.core.commons.services.webdav.servlets.RequestUtil;
import org.olat.core.gui.components.tree.GenericTreeModel;
import org.olat.core.gui.components.tree.TreeNode;
import org.olat.core.id.IdentityEnvironment;
import org.olat.core.logging.OLog;
import org.olat.core.logging.Tracing;
import org.olat.core.util.StringHelper;
import org.olat.core.util.vfs.MergeSource;
import org.olat.core.util.vfs.NamedContainerImpl;
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.VFSItem;
import org.olat.core.util.vfs.VFSManager;
import org.olat.core.util.vfs.callbacks.ReadOnlyCallback;
import org.olat.core.util.vfs.callbacks.VFSSecurityCallback;
import org.olat.core.util.vfs.filters.VFSItemFilter;
import org.olat.course.config.CourseConfig;
import org.olat.course.nodes.BCCourseNode;
import org.olat.course.nodes.CourseNode;
import org.olat.course.nodes.PFCourseNode;

dfurrer
committed
import org.olat.course.nodes.bc.BCCourseNodeEditController;
import org.olat.course.nodes.bc.FolderNodeCallback;
import org.olat.course.nodes.pf.manager.PFManager;
import org.olat.course.run.userview.NodeEvaluation;
import org.olat.course.run.userview.TreeEvaluation;
import org.olat.course.run.userview.UserCourseEnvironment;
import org.olat.course.run.userview.UserCourseEnvironmentImpl;

srosse
committed
import org.olat.course.run.userview.VisibleTreeFilter;
import org.olat.modules.sharedfolder.SharedFolderManager;
import org.olat.repository.RepositoryEntry;
import org.olat.repository.RepositoryManager;
import org.olat.repository.RepositoryService;
import org.olat.repository.model.RepositoryEntrySecurity;
import org.olat.resource.OLATResource;
/**

dfurrer
committed
*
* @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
*/
public class MergedCourseContainer extends MergeSource {
private static final OLog log = Tracing.createLoggerFor(MergedCourseContainer.class);
private final Long courseId;
private boolean courseReadOnly = false;
private boolean overrideReadOnly = false;
private final IdentityEnvironment identityEnv;
public MergedCourseContainer(Long courseId, String name) {
this(courseId, name, null, false);
}
public MergedCourseContainer(Long courseId, String name, IdentityEnvironment identityEnv) {
this(courseId, name, identityEnv, false);
}
public MergedCourseContainer(Long courseId, String name, IdentityEnvironment identityEnv, boolean overrideReadOnly) {
super(null, name);
this.courseId = courseId;
this.identityEnv = identityEnv;
this.overrideReadOnly = overrideReadOnly;
}
@Override
protected void init() {
ICourse course = CourseFactory.loadCourse(courseId);
if(course instanceof PersistingCourseImpl) {
init((PersistingCourseImpl)course);
}
}
protected void init(PersistingCourseImpl persistingCourse) {
super.init();
RepositoryEntry courseRe = persistingCourse.getCourseEnvironment().getCourseGroupManager().getCourseEntry();
courseReadOnly = !overrideReadOnly && (courseRe.getRepositoryEntryStatus().isClosed() || courseRe.getRepositoryEntryStatus().isUnpublished());
if(courseReadOnly) {
setLocalSecurityCallback(new ReadOnlyCallback());
}
if(identityEnv == null || identityEnv.getRoles().isOLATAdmin()) {
VFSContainer courseContainer = persistingCourse.getIsolatedCourseFolder();
if(courseReadOnly) {
courseContainer.setLocalSecurityCallback(new ReadOnlyCallback());
}
addContainersChildren(courseContainer, true);
RepositoryEntry re = persistingCourse.getCourseEnvironment().getCourseGroupManager().getCourseEntry();
RepositoryEntrySecurity reSecurity = RepositoryManager.getInstance()
.isAllowed(identityEnv.getIdentity(), identityEnv.getRoles(), re);
if(reSecurity.isEntryAdmin()) {
VFSContainer courseContainer = persistingCourse.getIsolatedCourseFolder();
if(courseReadOnly) {
courseContainer.setLocalSecurityCallback(new ReadOnlyCallback());
}
addContainersChildren(courseContainer, true);
}
initSharedFolder(persistingCourse);
// add all course building blocks of type BC to a virtual folder
MergeSource nodesContainer = new MergeSource(null, "_courseelementdata");
if(identityEnv == null) {
CourseNode rootNode = persistingCourse.getRunStructure().getRootNode();
addFoldersForAdmin(persistingCourse, nodesContainer, rootNode);
} else {
TreeEvaluation treeEval = new TreeEvaluation();
GenericTreeModel treeModel = new GenericTreeModel();
UserCourseEnvironment userCourseEnv = new UserCourseEnvironmentImpl(identityEnv, persistingCourse.getCourseEnvironment());
CourseNode rootCn = userCourseEnv.getCourseEnvironment().getRunStructure().getRootNode();
NodeEvaluation rootNodeEval = rootCn.eval(userCourseEnv.getConditionInterpreter(), treeEval, new VisibleTreeFilter());
TreeNode treeRoot = rootNodeEval.getTreeNode();
treeModel.setRootNode(treeRoot);
addFolders(persistingCourse, nodesContainer, treeRoot);
}
if (nodesContainer.getItems().size() > 0) {
addContainer(nodesContainer);
}
}
/**
* Grab any shared folder that is configured, but only when in unchecked
* security mode (no identity environment) or when the user has course
* admin rights
*
* @param persistingCourse
*/
private void initSharedFolder(PersistingCourseImpl persistingCourse) {
CourseConfig courseConfig = persistingCourse.getCourseConfig();
String sfSoftkey = courseConfig.getSharedFolderSoftkey();
if (StringHelper.containsNonWhitespace(sfSoftkey) && !CourseConfig.VALUE_EMPTY_SHAREDFOLDER_SOFTKEY.equals(sfSoftkey)) {
RepositoryEntry re = persistingCourse.getCourseEnvironment().getCourseGroupManager().getCourseEntry();
if(identityEnv == null || identityEnv.getRoles().isOLATAdmin() || RepositoryManager.getInstance().isOwnerOfRepositoryEntry(identityEnv.getIdentity(), re)) {
OLATResource sharedResource = CoreSpringFactory.getImpl(RepositoryService.class).loadRepositoryEntryResourceBySoftKey(sfSoftkey);
if (sharedResource != null) {
OlatRootFolderImpl sharedFolder = SharedFolderManager.getInstance().getSharedFolder(sharedResource);
if (sharedFolder != null) {
if(courseConfig.isSharedFolderReadOnlyMount() || courseReadOnly) {
sharedFolder.setLocalSecurityCallback(new ReadOnlyCallback());
}
//add local course folder's children as read/write source and any sharedfolder as subfolder
addContainer(new NamedContainerImpl("_sharedfolder", sharedFolder));
}
}
}
}
}
private void addFolders(PersistingCourseImpl course, MergeSource nodesContainer, TreeNode courseNode) {
if(courseNode == null) return;
for (int i = 0; i < courseNode.getChildCount(); i++) {
TreeNode child = (TreeNode)courseNode.getChildAt(i);
NodeEvaluation nodeEval;
if(child.getUserObject() instanceof NodeEvaluation) {
nodeEval = (NodeEvaluation)child.getUserObject();
} else {
continue;
}
if(nodeEval != null && nodeEval.getCourseNode() != null) {
CourseNode courseNodeChild = nodeEval.getCourseNode();
String folderName = RequestUtil.normalizeFilename(courseNodeChild.getShortTitle());
if (courseNodeChild instanceof BCCourseNode) {
final BCCourseNode bcNode = (BCCourseNode) courseNodeChild;
// add folder not to merge source. Use name and node id to have unique name
VFSContainer rootFolder = getBCContainer(course, bcNode, nodeEval, false);

dfurrer
committed
boolean canDownload = nodeEval.isCapabilityAccessible("download");
if(canDownload && rootFolder != null) {
if(courseReadOnly) {
rootFolder.setLocalSecurityCallback(new ReadOnlyCallback());
} else if(nodeEval.isCapabilityAccessible("upload")) {
//inherit the security callback from the course as for author
} else {
rootFolder.setLocalSecurityCallback(new ReadOnlyCallback());
}
folderName = getFolderName(nodesContainer, bcNode, folderName);
// Create a container for this node content and wrap it with a merge source which is attached to tree

dfurrer
committed
VFSContainer nodeContentContainer = new NamedContainerImpl(folderName, rootFolder);
MergeSource courseNodeContainer = new MergeSource(nodesContainer, folderName);
courseNodeContainer.addContainersChildren(nodeContentContainer, true);
nodesContainer.addContainer(courseNodeContainer);
// Do recursion for all children
addFolders(course, courseNodeContainer, child);
} else {
// For non-folder course nodes, add merge source (no files to show) ...
MergeSource courseNodeContainer = new MergeSource(null, folderName);
// , then do recursion for all children ...
addFolders(course, courseNodeContainer, child);
// ... but only add this container if it contains any children with at least one BC course node
if (courseNodeContainer.getItems().size() > 0) {
nodesContainer.addContainer(courseNodeContainer);
}
}
} else if (courseNodeChild instanceof PFCourseNode) {
final PFCourseNode pfNode = (PFCourseNode) courseNodeChild;
// add folder not to merge source. Use name and node id to have unique name
PFManager pfManager = CoreSpringFactory.getImpl(PFManager.class);
folderName = getFolderName(nodesContainer, pfNode, folderName);
MergeSource courseNodeContainer = new MergeSource(nodesContainer, folderName);
UserCourseEnvironment userCourseEnv = new UserCourseEnvironmentImpl(identityEnv, course.getCourseEnvironment());
VFSContainer rootFolder = pfManager.provideCoachOrParticipantContainer(pfNode, userCourseEnv,
identityEnv.getIdentity(), courseReadOnly);
VFSContainer nodeContentContainer = new NamedContainerImpl(folderName, rootFolder);
courseNodeContainer.addContainersChildren(nodeContentContainer, true);
addFolders(course, courseNodeContainer, child);
nodesContainer.addContainer(courseNodeContainer);
} else {
// For non-folder course nodes, add merge source (no files to show) ...
MergeSource courseNodeContainer = new MergeSource(null, folderName);
// , then do recursion for all children ...
addFolders(course, courseNodeContainer, child);
// ... but only add this container if it contains any children with at least one BC course node
if (courseNodeContainer.getItems().size() > 0) {
nodesContainer.addContainer(courseNodeContainer);
}
}
}
}
}
/**
* Internal method to recursively add all course building blocks of type
* BC to a given VFS container. This should only be used for an author view,
* it does not test for security.
* @param course
* @param nodesContainer
* @param courseNode
* @return container for the current course node
*/
private void addFoldersForAdmin(PersistingCourseImpl course, MergeSource nodesContainer, CourseNode courseNode) {
for (int i = 0; i < courseNode.getChildCount(); i++) {
CourseNode child = (CourseNode) courseNode.getChildAt(i);
String folderName = RequestUtil.normalizeFilename(child.getShortTitle());
if (child instanceof BCCourseNode) {
final BCCourseNode bcNode = (BCCourseNode) child;
// add folder not to merge source. Use name and node id to have unique name
VFSContainer rootFolder = getBCContainer(course, bcNode, null, true);
if(courseReadOnly) {
rootFolder.setLocalSecurityCallback(new ReadOnlyCallback());

dfurrer
committed
}
folderName = getFolderName(nodesContainer, bcNode, folderName);

dfurrer
committed
if(rootFolder != null) {
// Create a container for this node content and wrap it with a merge source which is attached to tree
VFSContainer nodeContentContainer = new NamedContainerImpl(folderName, rootFolder);
MergeSource courseNodeContainer = new MergeSource(nodesContainer, folderName);
courseNodeContainer.addContainersChildren(nodeContentContainer, true);
nodesContainer.addContainer(courseNodeContainer);
// Do recursion for all children
addFoldersForAdmin(course, courseNodeContainer, child);

dfurrer
committed
}
} else if (child instanceof PFCourseNode) {
final PFCourseNode pfNode = (PFCourseNode) child;
// add folder not to merge source. Use name and node id to have unique name
PFManager pfManager = CoreSpringFactory.getImpl(PFManager.class);
folderName = getFolderName(nodesContainer, pfNode, folderName);
MergeSource courseNodeContainer = new MergeSource(nodesContainer, folderName);
VFSContainer rootFolder = pfManager.provideAdminContainer(pfNode, course.getCourseEnvironment());
VFSContainer nodeContentContainer = new NamedContainerImpl(folderName, rootFolder);
courseNodeContainer.addContainersChildren(nodeContentContainer, true);
nodesContainer.addContainer(courseNodeContainer);
// Do recursion for all children
addFoldersForAdmin(course, courseNodeContainer, child);
} else {
// For non-folder course nodes, add merge source (no files to show) ...
MergeSource courseNodeContainer = new MergeSource(null, folderName);
// , then do recursion for all children ...
addFoldersForAdmin(course, courseNodeContainer, child);
// ... but only add this container if it contains any children with at least one BC course node
if (courseNodeContainer.getItems().size() > 0) {
nodesContainer.addContainer(courseNodeContainer);
}
}
}
}
/**
* Add node ident if multiple files have same name
*
* @param nodesContainer
* @param bcNode
* @param folderName
* @return
*/
private String getFolderName(MergeSource nodesContainer, CourseNode bcNode, String folderName) {
// add node ident if multiple files have same name
if (nodesContainer.getItems(new VFSItemFilter() {
@Override
public boolean accept(VFSItem vfsItem) {
return (bcNode.getShortTitle().equals(RequestUtil.normalizeFilename(bcNode.getShortTitle())));
}
}).size() > 0) {
folderName = folderName + " (" + bcNode.getIdent() + ")";
}
return folderName;
}
private VFSContainer getBCContainer(ICourse course, BCCourseNode bcNode, NodeEvaluation nodeEval, boolean isOlatAdmin) {
bcNode.updateModuleConfigDefaults(false);
// add folder not to merge source. Use name and node id to have unique name
VFSContainer rootFolder = null;
String subpath = bcNode.getModuleConfiguration().getStringValue(BCCourseNodeEditController.CONFIG_SUBPATH);
if(StringHelper.containsNonWhitespace(subpath)){
if(bcNode.isSharedFolder()){
// grab any shared folder that is configured
OlatRootFolderImpl sharedFolder = null;
String sfSoftkey = course.getCourseConfig().getSharedFolderSoftkey();
if (StringHelper.containsNonWhitespace(sfSoftkey) && !CourseConfig.VALUE_EMPTY_SHAREDFOLDER_SOFTKEY.equals(sfSoftkey)) {
RepositoryManager rm = RepositoryManager.getInstance();
RepositoryEntry re = rm.lookupRepositoryEntryBySoftkey(sfSoftkey, false);
if (re != null) {
sharedFolder = SharedFolderManager.getInstance().getSharedFolder(re.getOlatResource());
VFSContainer courseBase = sharedFolder;
subpath = subpath.replaceFirst("/_sharedfolder", "");
rootFolder = (VFSContainer) courseBase.resolve(subpath);
if(rootFolder != null && (course.getCourseConfig().isSharedFolderReadOnlyMount() || courseReadOnly)) {
rootFolder.setLocalSecurityCallback(new ReadOnlyCallback());
}
}
}
VFSContainer courseBase = course.getCourseBaseContainer();
rootFolder = (VFSContainer) courseBase.resolve("/coursefolder" + subpath);
}
}
if(bcNode.getModuleConfiguration().getBooleanSafe(BCCourseNodeEditController.CONFIG_AUTO_FOLDER)){
String path = BCCourseNode.getFoldernodePathRelToFolderBase(course.getCourseEnvironment(), bcNode);
rootFolder = new OlatRootFolderImpl(path, null);
if(nodeEval != null) {
rootFolder.setLocalSecurityCallback(new FolderNodeCallback(path, nodeEval, isOlatAdmin, false, null));
} else {
VFSSecurityCallback secCallback = VFSManager.findInheritedSecurityCallback(this);
if(secCallback != null) {
rootFolder.setLocalSecurityCallback(new OverrideQuotaSecurityCallback(path, secCallback));
}
}
}
return rootFolder;
}
private Object readResolve() {
try {
init();
return this;
} catch (Exception e) {
log.error("Cannot init the merged container of a course after deserialization", e);
return null;
}
}
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
private static class OverrideQuotaSecurityCallback implements VFSSecurityCallback {
private final String relPath;
private Quota overridenQuota;
private final VFSSecurityCallback secCallback;
public OverrideQuotaSecurityCallback(String relPath, VFSSecurityCallback secCallback) {
this.relPath = relPath;
this.secCallback = secCallback;
}
@Override
public boolean canRead() {
return secCallback.canRead();
}
@Override
public boolean canWrite() {
return secCallback.canWrite();
}
@Override
public boolean canCreateFolder() {
return secCallback.canCreateFolder();
}
@Override
public boolean canDelete() {
return secCallback.canDelete();
}
@Override
public boolean canList() {
return secCallback.canList();
}
@Override
public boolean canCopy() {
return secCallback.canCopy();
}
@Override
public boolean canDeleteRevisionsPermanently() {
return secCallback.canDeleteRevisionsPermanently();
}
@Override
public Quota getQuota() {
if(overridenQuota == null) {
QuotaManager qm = QuotaManager.getInstance();
overridenQuota = qm.getCustomQuota(relPath);
if (overridenQuota == null) {
Quota defQuota = qm.getDefaultQuota(QuotaConstants.IDENTIFIER_DEFAULT_NODES);
overridenQuota = qm.createQuota(relPath, defQuota.getQuotaKB(), defQuota.getUlLimitKB());
}
}
return overridenQuota;
}
@Override
public void setQuota(Quota quota) {
//
}
@Override
public SubscriptionContext getSubscriptionContext() {
return secCallback.getSubscriptionContext();
}
}