Newer
Older
/**
* 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.course;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.zip.ZipOutputStream;
import org.apache.poi.util.IOUtils;
import org.olat.basesecurity.BaseSecurity;
import org.olat.basesecurity.BaseSecurityManager;
import org.olat.basesecurity.Constants;
import org.olat.basesecurity.SecurityGroup;
import org.olat.commons.calendar.CalendarManager;
import org.olat.commons.calendar.CalendarManagerFactory;

srosse
committed
import org.olat.commons.calendar.notification.CalendarNotificationManager;
import org.olat.commons.calendar.ui.components.KalendarRenderWrapper;
import org.olat.core.CoreSpringFactory;
import org.olat.core.commons.fullWebApp.LayoutMain3ColsController;
import org.olat.core.commons.modules.bc.FolderConfig;
import org.olat.core.commons.modules.bc.vfs.OlatRootFolderImpl;
import org.olat.core.commons.persistence.DBFactory;
import org.olat.core.commons.services.taskexecutor.TaskExecutorManager;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.components.htmlheader.jscss.CustomCSS;
import org.olat.core.gui.components.stack.StackedController;
import org.olat.core.gui.components.tree.TreeNode;
import org.olat.core.gui.control.Controller;
import org.olat.core.gui.control.WindowControl;
import org.olat.core.gui.control.generic.layout.MainLayoutController;
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.BusinessControlFactory;
import org.olat.core.id.context.ContextEntry;
import org.olat.core.logging.AssertException;
import org.olat.core.logging.OLATRuntimeException;
import org.olat.core.logging.OLog;
import org.olat.core.logging.Tracing;
import org.olat.core.manager.BasicManager;
import org.olat.core.util.CodeHelper;
import org.olat.core.util.ExportUtil;
import org.olat.core.util.FileUtils;
import org.olat.core.util.Formatter;
import org.olat.core.util.ObjectCloner;
import org.olat.core.util.StringHelper;
import org.olat.core.util.UserSession;
import org.olat.core.util.Util;
import org.olat.core.util.WebappHelper;
import org.olat.core.util.cache.CacheWrapper;
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.event.MultiUserEvent;
import org.olat.core.util.nodes.INode;
import org.olat.core.util.notifications.NotificationsManager;
import org.olat.core.util.notifications.Publisher;
import org.olat.core.util.notifications.SubscriptionContext;
import org.olat.core.util.resource.OresHelper;
import org.olat.core.util.tree.TreeVisitor;
import org.olat.core.util.tree.Visitor;
import org.olat.core.util.vfs.VFSConstants;
import org.olat.core.util.vfs.VFSContainer;
import org.olat.core.util.vfs.VFSStatus;
import org.olat.core.util.xml.XStreamHelper;
import org.olat.course.archiver.ScoreAccountingHelper;
import org.olat.course.assessment.manager.UserCourseInformationsManager;
import org.olat.course.config.CourseConfig;
import org.olat.course.config.CourseConfigManagerImpl;
import org.olat.course.config.ui.courselayout.CourseLayoutHelper;
import org.olat.course.editor.EditorMainController;
import org.olat.course.editor.PublishProcess;
import org.olat.course.editor.StatusDescription;
import org.olat.course.groupsandrights.CourseGroupManager;
import org.olat.course.groupsandrights.PersistingCourseGroupManager;
import org.olat.course.nodes.AssessableCourseNode;
import org.olat.course.nodes.BCCourseNode;
import org.olat.course.nodes.CourseNode;
import org.olat.course.nodes.STCourseNode;
import org.olat.course.nodes.TACourseNode;
import org.olat.course.properties.CoursePropertyManager;
import org.olat.course.properties.PersistingCoursePropertyManager;
import org.olat.course.repository.ImportGlossaryReferencesController;
import org.olat.course.repository.ImportSharedfolderReferencesController;
import org.olat.course.run.RunMainController;
import org.olat.course.run.environment.CourseEnvironment;
import org.olat.course.statistic.AsyncExportManager;
import org.olat.course.tree.CourseEditorTreeModel;
import org.olat.course.tree.CourseEditorTreeNode;
import org.olat.course.tree.PublishTreeModel;
import org.olat.instantMessaging.InstantMessagingService;
import org.olat.instantMessaging.manager.ChatLogHelper;
import org.olat.modules.glossary.GlossaryManager;
import org.olat.modules.sharedfolder.SharedFolderManager;
import org.olat.repository.RepositoryEntry;
import org.olat.repository.RepositoryEntryImportExport;
import org.olat.repository.RepositoryManager;
import org.olat.resource.OLATResource;
import org.olat.resource.OLATResourceManager;
import org.olat.resource.references.ReferenceImpl;
import org.olat.resource.references.ReferenceManager;
import org.olat.testutils.codepoints.server.Codepoint;
import org.olat.user.UserManager;
import org.olat.util.logging.activity.LoggingResourceable;
/**
* Description: <BR>
* Use the course factory to create course run and edit controllers or to load a
* course from disk
*
* Initial Date: Oct 12, 2004
* @author Felix Jost
* @author guido
*/
public class CourseFactory extends BasicManager {
private static CacheWrapper<Long,PersistingCourseImpl> loadedCourses;
private static ConcurrentHashMap<Long, ModifyCourseEvent> modifyCourseEvents = new ConcurrentHashMap<Long, ModifyCourseEvent>();
public static final String COURSE_EDITOR_LOCK = "courseEditLock";
//this is the lock that must be aquired at course editing, copy course, export course, configure course.
private static Map<Long,PersistingCourseImpl> courseEditSessionMap = new ConcurrentHashMap<Long,PersistingCourseImpl>();
private static OLog log = Tracing.createLoggerFor(CourseFactory.class);
private static RepositoryManager repositoryManager;
private static OLATResourceManager olatResourceManager;
private static BaseSecurity securityManager;
private static ReferenceManager referenceManager;
private static GlossaryManager glossaryManager;
/**
* [used by spring]
*/
private CourseFactory(CoordinatorManager coordinatorManager, RepositoryManager repositoryManager, OLATResourceManager olatResourceManager,
BaseSecurity securityManager, ReferenceManager referenceManager, GlossaryManager glossaryManager) {
loadedCourses = coordinatorManager.getCoordinator().getCacher().getCache(CourseFactory.class.getSimpleName(), "courses");
CourseFactory.repositoryManager = repositoryManager;
CourseFactory.olatResourceManager = olatResourceManager;
CourseFactory.securityManager = securityManager;
CourseFactory.referenceManager = referenceManager;
CourseFactory.glossaryManager = glossaryManager;
}
/**
* Create a run controller for the given course resourceable
*
* @param ureq
* @param wControl
* @param re
* @param initialViewIdentifier if null the default view will be started,
* otherwise a controllerfactory type dependant view will be
* activated (subscription subtype)
* @return run controller for the given course resourceable
*/
public static MainLayoutController createLaunchController(UserRequest ureq, WindowControl wControl, final RepositoryEntry re) {
ICourse course = loadCourse(re.getOlatResource());
boolean isDebug = log.isDebug();
long startT = 0;
if(isDebug){
startT = System.currentTimeMillis();
}
MainLayoutController launchC = new RunMainController(ureq, wControl, course, re, true, true);
log.debug("Runview for [["+course.getCourseTitle()+"]] took [ms]"+(System.currentTimeMillis() - startT));
}
return launchC;
}
/**
* Create an editor controller for the given course resourceable
*
* @param ureq
* @param wControl
* @param olatResource
* @return editor controller for the given course resourceable; if the editor
* is already locked, it returns a controller with a lock message
*/
public static Controller createEditorController(UserRequest ureq, WindowControl wControl, StackedController stack,
OLATResourceable olatResource, CourseNode selectedNode) {
EditorMainController emc = new EditorMainController(ureq, wControl, course, stack, selectedNode);
if (!emc.getLockEntry().isSuccess()) {
// get i18n from the course runmaincontroller to say that this editor is
// already locked by another person
Translator translator = Util.createPackageTranslator(RunMainController.class, ureq.getLocale());
String lockerName = CoreSpringFactory.getImpl(UserManager.class).getUserDisplayName(emc.getLockEntry().getOwner());
wControl.setWarning(translator.translate("error.editoralreadylocked", new String[] { lockerName }));
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
return null;
}
//set the logger if editor is started
//since 5.2 groups / areas can be created from the editor -> should be logged.
emc.addLoggingResourceable(LoggingResourceable.wrap(course));
return emc;
}
/**
* Creates an empty course with a single root node. The course is linked to
* the resourceable ores.
*
* @param ores
* @param shortTitle Short title of root node
* @param longTitle Long title of root node
* @param learningObjectives Learning objectives of root node
* @return an empty course with a single root node.
*/
public static ICourse createEmptyCourse(OLATResourceable ores, String shortTitle, String longTitle, String learningObjectives) {
PersistingCourseImpl newCourse = new PersistingCourseImpl(ores.getResourceableId());
// Put new course in course cache
putCourseInCache(newCourse.getResourceableId() ,newCourse);
Structure initialStructure = new Structure();
CourseNode runRootNode = new STCourseNode();
runRootNode.setShortTitle(shortTitle);
runRootNode.setLongTitle(longTitle);
runRootNode.setLearningObjectives(learningObjectives);
initialStructure.setRootNode(runRootNode);
newCourse.setRunStructure(initialStructure);
newCourse.saveRunStructure();
CourseEditorTreeModel editorTreeModel = new CourseEditorTreeModel();
CourseEditorTreeNode editorRootNode = new CourseEditorTreeNode((CourseNode) ObjectCloner.deepCopy(runRootNode));
editorTreeModel.setRootNode(editorRootNode);
newCourse.setEditorTreeModel(editorTreeModel);
newCourse.saveEditorTreeModel();
return newCourse;
}
/**
* Gets the course from cache if already there, or loads the course and puts it into cache.
* To be called for the "CourseRun" model.
* @param resourceableId
* @return the course with the given id (the type is always
* CourseModule.class.toString())
*/
public static ICourse loadCourse(final Long resourceableId) {
if (resourceableId == null) throw new AssertException("No resourceable ID found.");
PersistingCourseImpl course = getCourseFromCache(resourceableId);
if (course == null) {
// o_clusterOK by:ld - load and put in cache in doInSync block to ensure
// that no invalidate cache event was missed
if (log.isDebug()) log.debug("try to load course with resourceableId=" + resourceableId);
OLATResourceable courseResourceable = OresHelper.createOLATResourceableInstance(PersistingCourseImpl.class, resourceableId);
course = CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync(courseResourceable, new SyncerCallback<PersistingCourseImpl>() {
public PersistingCourseImpl execute() {
PersistingCourseImpl theCourse = null;
theCourse = getCourseFromCache(resourceableId);
if (theCourse == null) {
long startTime = 0;
long endTime = 0;
if (log.isDebug()) startTime = System.currentTimeMillis();
theCourse = new PersistingCourseImpl(resourceableId);
theCourse.load();
if (log.isDebug()) endTime = System.currentTimeMillis();
putCourseInCache(resourceableId, theCourse);
long diff = 0;
if (log.isDebug()) diff = Long.valueOf(endTime - startTime);
if (log.isDebug()) log.debug("[[" + resourceableId + "[[" + diff + "[[" + theCourse.getCourseTitle());
}
return theCourse;
}
});
}
return course;
}
/**
* Load the course for the given course resourceable
*
* @param olatResource
* @return the course for the given course resourceable
*/
public static ICourse loadCourse(OLATResourceable olatResource) {
Long resourceableId = olatResource.getResourceableId();
return loadCourse(resourceableId);
}
/**
*
* @param resourceableId
* @return the PersistingCourseImpl instance for the input key.
*/
static PersistingCourseImpl getCourseFromCache(Long resourceableId) { //o_clusterOK by:ld
return loadedCourses.get(resourceableId);
}
/**
* Puts silent.
* @param resourceableId
* @param course
*/
static void putCourseInCache(Long resourceableId, PersistingCourseImpl course) { //o_clusterOK by:ld
loadedCourses.put(resourceableId, course);
log.debug("putCourseInCache ");
}
/**
*
* @param resourceableId
*/
private static void removeFromCache(Long resourceableId) { //o_clusterOK by: ld
loadedCourses.remove(resourceableId);
log.debug("removeFromCache");
}
/**
* Puts the current course in the local cache and removes it from other caches (other cluster nodes).
* @param resourceableId
* @param course
*/
private static void updateCourseInCache(Long resourceableId, PersistingCourseImpl course) { //o_clusterOK by:ld
loadedCourses.update(resourceableId, course);
log.debug("updateCourseInCache");
}
/**
* Delete a course including its course folder and all references to resources
* this course holds.
*
* @param res
*/
public static void deleteCourse(OLATResourceable res) {
final long start = System.currentTimeMillis();
log.info("deleteCourse: starting to delete course. res="+res);
// find all references to course
List<ReferenceImpl> refs = referenceManager.getReferences(res);
for (Iterator<ReferenceImpl> iter = refs.iterator(); iter.hasNext();) {
ReferenceImpl ref = (ReferenceImpl) iter.next();
referenceManager.delete(ref);
}
PersistingCourseImpl course = null;
try {
course = (PersistingCourseImpl)loadCourse(res);
} catch (CorruptedCourseException e) {
log.error("Try to delete a corrupted course, I make want I can.");
}
// call cleanupOnDelete for nodes
if(course != null) {
Visitor visitor = new NodeDeletionVisitor(course);
TreeVisitor tv = new TreeVisitor(visitor, course.getRunStructure().getRootNode(), true);
tv.visitAll();
}
OLATResourceable assessmentOres = OresHelper.createOLATResourceableInstance(CourseModule.ORES_COURSE_ASSESSMENT, res.getResourceableId());
NotificationsManager.getInstance().deletePublishersOf(assessmentOres);
// delete all course notifications
NotificationsManager.getInstance().deletePublishersOf(res);
//delete calendar subscription
clearCalenderSubscriptions(res);
// delete course configuration (not really usefull, the config is in
// the course folder which is deleted right after)
if(course != null) {
CourseConfigManagerImpl.getInstance().deleteConfigOf(course);
}
//clean up tasks
OLATResource resource = course.getCourseEnvironment().getCourseGroupManager().getCourseResource();
CoreSpringFactory.getImpl(TaskExecutorManager.class).delete(resource);
CourseGroupManager courseGroupManager = PersistingCourseGroupManager.getInstance(res);
courseGroupManager.deleteCourseGroupmanagement();
CoursePropertyManager propertyManager = PersistingCoursePropertyManager.getInstance(res);
propertyManager.deleteAllCourseProperties();
// delete course calendar
CalendarManager calManager = CalendarManagerFactory.getInstance().getCalendarManager();
calManager.deleteCourseCalendar(res);
// delete IM messages
CoreSpringFactory.getImpl(InstantMessagingService.class).deleteMessages(res);
// cleanup cache
removeFromCache(res.getResourceableId());
//TODO: ld: broadcast event: DeleteCourseEvent
// Everything is deleted, so we could get rid of course logging
// with the change in user audit logging - which now all goes into a DB
// we no longer do this though!
// delete course directory
VFSContainer fCourseBasePath = getCourseBaseContainer(res.getResourceableId());
VFSStatus status = fCourseBasePath.delete();
boolean deletionSuccessful = (status == VFSConstants.YES || status == VFSConstants.SUCCESS);
log.info("deleteCourse: finished deletion. res="+res+", deletion successful: "+deletionSuccessful+", duration: "+(System.currentTimeMillis()-start)+" ms.");
}
/**
* Checks all learning group calendars and the course calendar for publishers (of subscriptions)
* and sets their state to "1" which indicates that the ressource is deleted.
*/
private static void clearCalenderSubscriptions(OLATResourceable res) {
//set Publisher state to 1 (= ressource is deleted) for all calendars of the course
CalendarManager calMan = CalendarManagerFactory.getInstance().getCalendarManager();

srosse
committed
CalendarNotificationManager notificationManager = CoreSpringFactory.getImpl(CalendarNotificationManager.class);
NotificationsManager nfm = NotificationsManager.getInstance();
CourseGroupManager courseGroupManager = PersistingCourseGroupManager.getInstance(res);
List<BusinessGroup> learningGroups = courseGroupManager.getAllBusinessGroups();
//all learning and right group calendars
for (BusinessGroup bg : learningGroups) {
KalendarRenderWrapper calRenderWrapper = calMan.getGroupCalendar(bg);

srosse
committed
SubscriptionContext subsContext = notificationManager.getSubscriptionContext(calRenderWrapper);
Publisher pub = nfm.getPublisher(subsContext);
if (pub != null) {
pub.setState(1); //int 0 is OK -> all other is not OK
}
}
//the course calendar
try {
/**
* TODO:gs 2010-01-26
* OLAT-4947: if we do not have an repo entry we get an exception here.
* This is normal in the case of courseimport and click canceling.
*/
KalendarRenderWrapper courseCalendar = calMan.getCalendarForDeletion(res);
if(courseCalendar != null) {

srosse
committed
SubscriptionContext subContext = notificationManager.getSubscriptionContext(courseCalendar, res);
OLATResourceable oresToDelete = OresHelper.createOLATResourceableInstance(subContext.getResName(), subContext.getResId());
nfm.deletePublishersOf(oresToDelete);
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
}
} catch (AssertException e) {
//if we have a broken course (e.g. canceled import or no repo entry somehow) skip calendar deletion...
}
}
/**
* Copies a course. More specifically, the run and editor structures and the
* course folder will be copied to create a new course.
*
*
* @param sourceRes
* @param ureq
* @return copy of the course.
*/
public static OLATResourceable copyCourse(OLATResourceable sourceRes, UserRequest ureq) {
PersistingCourseImpl sourceCourse = (PersistingCourseImpl) loadCourse(sourceRes);
OLATResourceable targetRes = OLATResourceManager.getInstance().createOLATResourceInstance(CourseModule.class);
PersistingCourseImpl targetCourse = new PersistingCourseImpl(targetRes.getResourceableId());
File fTargetCourseBasePath = targetCourse.getCourseBaseContainer().getBasefile();
synchronized (sourceCourse) { // o_clusterNOK - cannot be solved with doInSync since could take too long (leads to error: "Lock wait timeout exceeded")
// copy configuration
CourseConfig courseConf = CourseConfigManagerImpl.getInstance().copyConfigOf(sourceCourse);
targetCourse.setCourseConfig(courseConf);
// save structures
targetCourse.setRunStructure((Structure) XStreamHelper.xstreamClone(sourceCourse.getRunStructure()));
targetCourse.saveRunStructure();
targetCourse.setEditorTreeModel((CourseEditorTreeModel) XStreamHelper.xstreamClone(sourceCourse.getEditorTreeModel()));
targetCourse.saveEditorTreeModel();
Codepoint.codepoint(CourseFactory.class, "copyCourseAfterSaveTreeModel");
// copy course folder
File fSourceCourseFolder = sourceCourse.getIsolatedCourseFolder().getBasefile();
if (fSourceCourseFolder.exists()) FileUtils.copyDirToDir(fSourceCourseFolder, fTargetCourseBasePath, false, "copy course folder");
// copy folder nodes directories
File fSourceFoldernodesFolder = new File(FolderConfig.getCanonicalRoot()
+ BCCourseNode.getFoldernodesPathRelToFolderBase(sourceCourse.getCourseEnvironment()));
if (fSourceFoldernodesFolder.exists()) FileUtils.copyDirToDir(fSourceFoldernodesFolder, fTargetCourseBasePath, false, "copy folder nodes directories");
// copy task folder directories
File fSourceTaskfoldernodesFolder = new File(FolderConfig.getCanonicalRoot()
+ TACourseNode.getTaskFoldersPathRelToFolderRoot(sourceCourse.getCourseEnvironment()));
if (fSourceTaskfoldernodesFolder.exists()) FileUtils.copyDirToDir(fSourceTaskfoldernodesFolder, fTargetCourseBasePath, false, "copy task folder directories");
//make sure the DB connection is available after this point
DBFactory.getInstance(false).commitAndCloseSession();
List<ReferenceImpl> refs = referenceManager.getReferences(sourceCourse);
int count = 0;
for (ReferenceImpl ref: refs) {
referenceManager.addReference(targetCourse, ref.getTarget(), ref.getUserdata());
if(count % 20 == 0) {
DBFactory.getInstance(false).intermediateCommit();
}
}
}
return targetRes;
}
/**
* Exports an entire course to a zip file.
*
* @param sourceRes
* @param fTargetZIP
* @return true if successfully exported, false otherwise.
*/
public static void exportCourseToZIP(OLATResourceable sourceRes, File fTargetZIP, boolean runtimeDatas, boolean backwardsCompatible) {
PersistingCourseImpl sourceCourse = (PersistingCourseImpl) loadCourse(sourceRes);
// add files to ZIP
File fExportDir = new File(WebappHelper.getTmpDir(), CodeHelper.getUniqueID());
log.info("Export folder: " + fExportDir);
synchronized (sourceCourse) { //o_clusterNOK - cannot be solved with doInSync since could take too long (leads to error: "Lock wait timeout exceeded")
OLATResource courseResource = sourceCourse.getCourseEnvironment().getCourseGroupManager().getCourseResource();
sourceCourse.exportToFilesystem(courseResource, fExportDir, runtimeDatas, backwardsCompatible);
Set<String> fileSet = new HashSet<String>();
String[] files = fExportDir.list();
for (int i = 0; i < files.length; i++) {
fileSet.add(files[i]);
}
ZipUtil.zip(fileSet, fExportDir, fTargetZIP, false);
log.info("Delete export folder: " + fExportDir);
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
FileUtils.deleteDirsAndFiles(fExportDir, true, true);
}
}
/**
* Import a course from a ZIP file.
*
* @param ores
* @param zipFile
* @return New Course.
*/
public static ICourse importCourseFromZip(OLATResourceable ores, File zipFile) {
// Generate course with filesystem
PersistingCourseImpl newCourse = new PersistingCourseImpl(ores.getResourceableId());
CourseConfigManagerImpl.getInstance().deleteConfigOf(newCourse);
// Unzip course strucure in new course
File fCanonicalCourseBasePath = newCourse.getCourseBaseContainer().getBasefile();
if (ZipUtil.unzip(zipFile, fCanonicalCourseBasePath)) {
// Load course strucure now
try {
newCourse.load();
CourseConfig cc = CourseConfigManagerImpl.getInstance().loadConfigFor(newCourse);
//newCourse is not in cache yet, so we cannot call setCourseConfig()
newCourse.setCourseConfig(cc);
putCourseInCache(newCourse.getResourceableId(), newCourse);
return newCourse;
} catch (AssertException ae) {
// ok failed, cleanup below
// better logging to search error
log.error("rollback importCourseFromZip",ae);
}
}
// cleanup if not successfull
FileUtils.deleteDirsAndFiles(fCanonicalCourseBasePath, true, true);
return null;
}
/**
* Deploys a course from an exported course ZIP file. This process is unatended and
* therefore relies on some default assumptions on how to setup the entry and add
* any referenced resources to the repository.
*
* @param exportedCourseZIPFile
*/
public static RepositoryEntry deployCourseFromZIP(File exportedCourseZIPFile, int access) {
return deployCourseFromZIP(exportedCourseZIPFile, "administrator", null, access);
}
public static RepositoryEntry deployCourseFromZIP(File exportedCourseZIPFile, String initialAuthor, String softKey, int access) {
// create the course instance
OLATResource newCourseResource = olatResourceManager.createOLATResourceInstance(CourseModule.class);
ICourse course = CourseFactory.importCourseFromZip(newCourseResource, exportedCourseZIPFile);
// course is now also in course cache!
if (course == null) {
log.error("Error deploying course from ZIP: " + exportedCourseZIPFile.getAbsolutePath());

srosse
committed
File courseExportData = course.getCourseExportDataDir().getBasefile();
// get the export data directory
// create the repository entry
RepositoryEntry re = repositoryManager.createRepositoryEntryInstance("administrator");
RepositoryEntryImportExport importExport = new RepositoryEntryImportExport(courseExportData);
if(!StringHelper.containsNonWhitespace(softKey)) {
softKey = importExport.getSoftkey();
}
RepositoryEntry existingEntry = repositoryManager.lookupRepositoryEntryBySoftkey(softKey, false);
if (existingEntry != null) {
log.info("RepositoryEntry with softkey " + softKey + " already exists. Course will not be deployed.");
//seem to be a problem
UserCourseInformationsManager userCourseInformationsManager = CoreSpringFactory.getImpl(UserCourseInformationsManager.class);
userCourseInformationsManager.deleteUserCourseInformations(existingEntry);
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
CourseFactory.deleteCourse(newCourseResource);
return existingEntry;
}
// ok, continue import
newCourseResource = olatResourceManager.findOrPersistResourceable(newCourseResource);
re.setOlatResource(newCourseResource);
re.setSoftkey(softKey);
re.setInitialAuthor(importExport.getInitialAuthor());
re.setDisplayname(importExport.getDisplayName());
re.setResourcename(importExport.getResourceName());
re.setDescription(importExport.getDescription());
re.setCanLaunch(true);
// set access configuration
re.setAccess(access);
// create security group
SecurityGroup ownerGroup = securityManager.createAndPersistSecurityGroup();
// member of this group may modify member's membership
securityManager.createAndPersistPolicy(ownerGroup, Constants.PERMISSION_ACCESS, ownerGroup);
// members of this group are always authors also
securityManager.createAndPersistPolicy(ownerGroup, Constants.PERMISSION_HASROLE, Constants.ORESOURCE_AUTHOR);
securityManager.addIdentityToSecurityGroup(securityManager.findIdentityByName("administrator"), ownerGroup);
re.setOwnerGroup(ownerGroup);
// save the repository entry
repositoryManager.saveRepositoryEntry(re);
// Create course admin policy for owner group of repository entry
// -> All owners of repository entries are course admins
securityManager.createAndPersistPolicy(re.getOwnerGroup(), Constants.PERMISSION_ADMIN, re.getOlatResource());
//fxdiff VCRP-1,2: access control of resources
// create security group for tutors / coaches
SecurityGroup tutorGroup = securityManager.createAndPersistSecurityGroup();
// member of this group may modify member's membership
securityManager.createAndPersistPolicy(tutorGroup, Constants.PERMISSION_ACCESS, re.getOlatResource());
securityManager.createAndPersistPolicy(tutorGroup, Constants.PERMISSION_COACH, re.getOlatResource());
// members of this group are always tutors also
securityManager.createAndPersistPolicy(tutorGroup, Constants.PERMISSION_HASROLE, Constants.ORESOURCE_TUTOR);
re.setTutorGroup(tutorGroup);
// create security group for participants
SecurityGroup participantGroup = securityManager.createAndPersistSecurityGroup();
// member of this group may modify member's membership
securityManager.createAndPersistPolicy(participantGroup, Constants.PERMISSION_ACCESS, re.getOlatResource());
securityManager.createAndPersistPolicy(participantGroup, Constants.PERMISSION_PARTI, re.getOlatResource());
// members of this group are always participants also
securityManager.createAndPersistPolicy(participantGroup, Constants.PERMISSION_HASROLE, Constants.ORESOURCE_PARTICIPANT);
re.setParticipantGroup(participantGroup);

srosse
committed
//import groups
course = openCourseEditSession(course.getResourceableId());
// create group management
CourseGroupManager cgm = course.getCourseEnvironment().getCourseGroupManager();
// import groups
cgm.importCourseBusinessGroups(courseExportData);
// deploy any referenced repository entries of the editor structure. This will also
// include any references in the run structure, since any node in the runstructure is also
// present in the editor structure.
deployReferencedRepositoryEntries(courseExportData, course,
(CourseEditorTreeNode)course.getEditorTreeModel().getRootNode());
// register any references in the run structure. The referenced entries have been
// previousely deplyed (as part of the editor structure deployment process - see above method call)
registerReferences(course, course.getRunStructure().getRootNode());
// import shared folder references
deployReferencedSharedFolders(courseExportData, course);
// import glossary references
deployReferencedGlossary(courseExportData, course);
closeCourseEditSession(course.getResourceableId(), true);
// cleanup export data
FileUtils.deleteDirsAndFiles(courseExportData, true, true);
log.info("Successfully deployed course " + re.getDisplayname() + " from ZIP: " + exportedCourseZIPFile.getAbsolutePath());
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
return re;
}
/**
* Unattended deploy any referenced repository entries.
*
* @param importDirectory
* @param course
* @param currentNode
*/
private static void deployReferencedRepositoryEntries(File importDirectory, ICourse course, CourseEditorTreeNode currentNode) {
for (int i = 0; i < currentNode.getChildCount(); i++) {
CourseEditorTreeNode childNode = (CourseEditorTreeNode)currentNode.getChildAt(i);
childNode.getCourseNode().importNode(importDirectory, course, true, null, null);
deployReferencedRepositoryEntries(importDirectory, course, childNode);
}
}
/**
* Register any referenced repository entries.
* @param course
* @param currentNode
*/
private static void registerReferences(ICourse course, CourseNode currentNode) {
for (int i = 0; i < currentNode.getChildCount(); i++) {
CourseNode childNode = (CourseNode)currentNode.getChildAt(i);
if (childNode.needsReferenceToARepositoryEntry()) {
referenceManager.addReference(course,
childNode.getReferencedRepositoryEntry().getOlatResource(), childNode.getIdent());
}
registerReferences(course, childNode);
}
}
private static void deployReferencedSharedFolders(File importDirectory, ICourse course) {
CourseConfig cc = course.getCourseEnvironment().getCourseConfig();
if (!cc.hasCustomSharedFolder()) return;
RepositoryEntryImportExport importExport = SharedFolderManager.getInstance()
.getRepositoryImportExport(importDirectory);
Identity owner = BaseSecurityManager.getInstance().findIdentityByName("administrator");
ImportSharedfolderReferencesController.doImport(importExport, course, false, owner);
}
/**
* Deploy referenced glossaries using the administrator account as owner
* @param importDirectory
* @param course
*/
private static void deployReferencedGlossary(File importDirectory, ICourse course) {
CourseConfig cc = course.getCourseEnvironment().getCourseConfig();
if (!cc.hasGlossary()) return;
RepositoryEntryImportExport importExport = glossaryManager.getRepositoryImportExport(importDirectory);
Identity owner = securityManager.findIdentityByName("administrator");
ImportGlossaryReferencesController.doImport(importExport, course, false, owner);
}
/**
* Publish the course with some standard options
* @param course
* @param locale
* @param identity
*/
public static void publishCourse(ICourse course, int access, boolean membersOnly, Identity identity, Locale locale) {
CourseEditorTreeModel cetm = course.getEditorTreeModel();
PublishProcess publishProcess = PublishProcess.getInstance(course, cetm, locale);
PublishTreeModel publishTreeModel = publishProcess.getPublishTreeModel();
int newAccess = (access < RepositoryEntry.ACC_OWNERS || access > RepositoryEntry.ACC_USERS_GUESTS)
? RepositoryEntry.ACC_USERS : access;
//access rule -> all users can the see course
//RepositoryEntry.ACC_OWNERS
//only owners can the see course
//RepositoryEntry.ACC_OWNERS_AUTHORS //only owners and authors can the see course
//RepositoryEntry.ACC_USERS_GUESTS // users and guests can see the course
//fxdiff VCRP-1,2: access control of resources
publishProcess.changeGeneralAccess(null, newAccess, membersOnly);
if (publishTreeModel.hasPublishableChanges()) {
List<String>nodeToPublish = new ArrayList<String>();
visitPublishModel(publishTreeModel.getRootNode(), publishTreeModel, nodeToPublish);
publishProcess.createPublishSetFor(nodeToPublish);
StatusDescription[] status = publishProcess.testPublishSet(locale);
//publish not possible when there are errors
for(int i = 0; i < status.length; i++) {
if(status[i].isError()) {
log.error("Status error by publish: " + status[i].getLongDescription(locale));
return;
}
try {
course = CourseFactory.openCourseEditSession(course.getResourceableId());
publishProcess.applyPublishSet(identity, locale);
} catch(Exception e) {
log.error("", e);
} finally {
closeCourseEditSession(course.getResourceableId(), true);
}
}
}
/**
* Get a details form for a given course resourceable
*
* @param res
* @param ureq
* @return details component displaying details of the course.
*/
public static Controller getDetailsForm(UserRequest ureq, WindowControl wControl, OLATResourceable res) {
// course does not provide a details component, this is somehow hardcoded in the
// RepositoryDetailsController
return null;
}
/**
* Create a user locale dependent help-course run controller
*
* @param ureq The user request
* @param wControl The current window controller
* @return The help-course run controller
*/
public static Controller createHelpCourseLaunchController(UserRequest ureq, WindowControl wControl) {
// Find repository entry for this course
String helpCourseSoftKey = CourseModule.getHelpCourseSoftKey();
RepositoryManager rm = RepositoryManager.getInstance();
RepositoryEntry entry = null;
if (StringHelper.containsNonWhitespace(helpCourseSoftKey)) {
entry = rm.lookupRepositoryEntryBySoftkey(helpCourseSoftKey, false);
}
if (entry == null) {
Translator translator = Util.createPackageTranslator(CourseFactory.class, ureq.getLocale());
wControl.setError(translator.translate("error.helpcourse.not.configured"));
// create empty main controller
LayoutMain3ColsController emptyCtr = new LayoutMain3ColsController(ureq, wControl, null, null, null, null);
return emptyCtr;
} else {
// Increment launch counter
rm.incrementLaunchCounter(entry);
OLATResource ores = entry.getOlatResource();
ICourse course = loadCourse(ores);
ContextEntry ce = BusinessControlFactory.getInstance().createContextEntry(entry);
WindowControl bwControl = BusinessControlFactory.getInstance().createBusinessWindowControl(ce, wControl);
RunMainController launchC = new RunMainController(ureq, bwControl, course, entry, false, false);
return launchC;
}
}
/**
* visit all nodes in the specified course and make them archiving any data
* into the identity's export directory.
*
* @param res
* @param charset
* @param locale
* @param identity
*/
public static void archiveCourse(OLATResourceable res, String charset, Locale locale, Identity identity) {
RepositoryEntry courseRe = RepositoryManager.getInstance().lookupRepositoryEntry(res, false);
PersistingCourseImpl course = (PersistingCourseImpl) loadCourse(res);
File exportDirectory = CourseFactory.getOrCreateDataExportDirectory(identity, course.getCourseTitle());
boolean isOLATAdmin = BaseSecurityManager.getInstance().isIdentityPermittedOnResourceable(identity, Constants.PERMISSION_HASROLE, Constants.ORESOURCE_ADMIN);
boolean isOresOwner = RepositoryManager.getInstance().isOwnerOfRepositoryEntry(identity, courseRe);
boolean isOresInstitutionalManager = RepositoryManager.getInstance().isInstitutionalRessourceManagerFor(courseRe, identity);
archiveCourse(identity, course, charset, locale, exportDirectory, isOLATAdmin, isOresOwner, isOresInstitutionalManager);
}
/**
* visit all nodes in the specified course and make them archiving any data
* into the identity's export directory.
*
* @param res
* @param charset
* @param locale
* @param identity
*/
public static void archiveCourse(Identity archiveOnBehalfOf, ICourse course, String charset, Locale locale, File exportDirectory, boolean isOLATAdmin, boolean... oresRights) {
// archive course results overview
List<Identity> users = ScoreAccountingHelper.loadUsers(course.getCourseEnvironment());
List<AssessableCourseNode> nodes = ScoreAccountingHelper.loadAssessableNodes(course.getCourseEnvironment());
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
String result = ScoreAccountingHelper.createCourseResultsOverviewTable(users, nodes, course, locale);
String fileName = ExportUtil.createFileNameWithTimeStamp(course.getCourseTitle(), "xls");
ExportUtil.writeContentToFile(fileName, result, exportDirectory, charset);
// archive all nodes content
Visitor archiveV = new NodeArchiveVisitor(locale, course, exportDirectory, charset);
TreeVisitor tv = new TreeVisitor(archiveV, course.getRunStructure().getRootNode(), true);
tv.visitAll();
// archive all course log files
//OLATadmin gets all logfiles independent of the visibility configuration
boolean isOresOwner = (oresRights.length > 0)?oresRights[0]:false;
boolean isOresInstitutionalManager = (oresRights.length > 1)?oresRights[1]:false;
boolean aLogV = isOresOwner || isOresInstitutionalManager || isOLATAdmin;
boolean uLogV = isOLATAdmin;
boolean sLogV = isOresOwner || isOresInstitutionalManager || isOLATAdmin;
// make an intermediate commit here to make sure long running course log export doesn't
// cause db connection timeout to be triggered
//@TODO transactions/backgroundjob:
// rework when backgroundjob infrastructure exists
DBFactory.getInstance(false).intermediateCommit();
AsyncExportManager.getInstance().asyncArchiveCourseLogFiles(archiveOnBehalfOf, new Runnable() {
public void run() {
// that's fine, I dont need to do anything here
};
}, course.getResourceableId(), exportDirectory.getPath(), null, null, aLogV, uLogV, sLogV, charset, null, null);
PersistingCourseGroupManager.getInstance(course).archiveCourseGroups(exportDirectory);
CoreSpringFactory.getImpl(ChatLogHelper.class).archive(course, exportDirectory);
}
/**
* Returns the data export directory. If the directory does not yet exist the
* directory will be created
*
* @param ureq The user request
* @param courseName The course name or title. Will be used as directory name
* @return The file representing the dat export directory
*/
public static File getOrCreateDataExportDirectory(Identity identity, String courseName) {
String courseFolder = StringHelper.transformDisplayNameToFileSystemName(courseName);
// folder where exported user data should be put
File exportFolder = new File(FolderConfig.getCanonicalRoot() + FolderConfig.getUserHomes() + "/" + identity.getName() + "/private/archive/"
+ courseFolder);
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
if (exportFolder.exists()) {
if (!exportFolder.isDirectory()) { throw new OLATRuntimeException(ExportUtil.class, "File " + exportFolder.getAbsolutePath()
+ " already exists but it is not a folder!", null); }
} else {
exportFolder.mkdirs();
}
return exportFolder;
}
/**
* Returns the data export directory.
*
* @param ureq The user request
* @param courseName The course name or title. Will be used as directory name
* @return The file representing the dat export directory
*/
public static File getDataExportDirectory(Identity identity, String courseName) {
File exportFolder = new File( // folder where exported user data should be
// put
FolderConfig.getCanonicalRoot() + FolderConfig.getUserHomes() + "/" + identity.getName() + "/private/archive/"
+ Formatter.makeStringFilesystemSave(courseName));
return exportFolder;
}
/**
* Returns the personal folder of the given identity.
* <p>
* The idea of this method is to match the first part of what
* getOrCreateDataExportDirectory() returns.
* <p>
* @param identity
* @return
*/
public static File getPersonalDirectory(Identity identity) {
if (identity==null) {
return null;
}
return new File(FolderConfig.getCanonicalRoot() + FolderConfig.getUserHomes() + "/" + identity.getName());
}
/**
* Returns the data export directory. If the directory does not yet exist the
* directory will be created
*
* @param ureq The user request
* @param courseName The course name or title. Will be used as directory name
* @return The file representing the dat export directory
*/
public static File getOrCreateStatisticDirectory(Identity identity, String courseName) {
File exportFolder = new File( // folder where exported user data should be
// put
FolderConfig.getCanonicalRoot() + FolderConfig.getUserHomes() + "/" + identity.getName() + "/private/statistics/"
+ Formatter.makeStringFilesystemSave(courseName));
if (exportFolder.exists()) {
if (!exportFolder.isDirectory()) { throw new OLATRuntimeException(ExportUtil.class, "File " + exportFolder.getAbsolutePath()
+ " already exists but it is not a folder!", null); }
} else {
exportFolder.mkdirs();
}
return exportFolder;
}
/**
* Stores the editor tree model AND the run structure (both xml files). Called at publish.
* @param resourceableId
*/
public static void saveCourse(final Long resourceableId) {
if (resourceableId == null) throw new AssertException("No resourceable ID found.");
PersistingCourseImpl theCourse = getCourseEditSession(resourceableId);
if(theCourse!=null) {
//o_clusterOK by: ld (although the course is locked for editing, we still have to insure that load course is synchronized)
CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync(theCourse, new SyncerExecutor(){