/** * 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.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.olat.core.commons.persistence.DBFactory; import org.olat.core.commons.services.notifications.SubscriptionContext; import org.olat.core.configuration.AbstractOLATModule; import org.olat.core.configuration.PersistedProperties; import org.olat.core.helpers.Settings; import org.olat.core.id.Identity; import org.olat.core.id.OLATResourceable; import org.olat.core.util.coordinate.CoordinatorManager; import org.olat.core.util.event.FrameworkStartedEvent; import org.olat.core.util.event.FrameworkStartupEventChannel; import org.olat.core.util.event.GenericEventListener; import org.olat.core.util.resource.OresHelper; import org.olat.course.assessment.AssessmentManager; import org.olat.course.nodes.CourseNode; import org.olat.course.run.environment.CourseEnvironment; import org.olat.properties.Property; import org.olat.properties.PropertyManager; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryManager; import org.olat.repository.RepositoryService; import org.olat.resource.OLATResource; import org.olat.resource.OLATResourceManager; /** * Initial Date: 02.09.2005 <br> * * @author Mike Stock * @author guido * @author Florian Gnägi */ public class CourseModule extends AbstractOLATModule { private static boolean courseChatEnabled; private static boolean displayParticipantsCount; // Repository types public static String ORES_TYPE_COURSE = OresHelper.calculateTypeName(CourseModule.class); private static OLATResourceable ORESOURCEABLE_TYPE_COURSE = OresHelper.lookupType(CourseModule.class); public static final String ORES_COURSE_ASSESSMENT = OresHelper.calculateTypeName(AssessmentManager.class); private static String helpCourseSoftkey; private static CoordinatorManager coordinatorManager; private Map<String, RepositoryEntry> deployedCourses; private boolean deployCoursesEnabled; private PropertyManager propertyManager; private CourseFactory courseFactory; private Map<String, String> logVisibilities; private List<DeployableCourseExport> deployableCourseExports; private RepositoryService repositoryService; private OLATResourceManager olatResourceManager; /** * [used by spring] */ private CourseModule(CoordinatorManager coordinatorManager, PropertyManager propertyManager, CourseFactory courseFactory, RepositoryService repositoryService, OLATResourceManager olatResourceManager) { CourseModule.coordinatorManager = coordinatorManager; this.propertyManager = propertyManager; this.courseFactory = courseFactory; this.repositoryService = repositoryService; this.olatResourceManager = olatResourceManager; coordinatorManager.getCoordinator().getEventBus().registerFor(this, null, FrameworkStartupEventChannel.getStartupEventChannel()); } /** * Courses are deployed after the startup has completed. * */ @Override public void event(org.olat.core.gui.control.Event event) { //do not deploy courses/help course if in JUnit Mode. if (Settings.isJUnitTest()) return; if (event instanceof FrameworkStartedEvent && ((FrameworkStartedEvent) event).isEventOnThisNode()) { // Deploy demo courses logInfo("Received FrameworkStartedEvent and is on same node, will start deploying demo courses..."); deployCoursesFromCourseExportFiles(); } //also in startup event processing intermediateCommit DBFactory.getInstance(false).intermediateCommit(); } /** * [used by spring] */ public void setCourseExportFiles(List<DeployableCourseExport> deployableCourseExports) { this.deployableCourseExports = deployableCourseExports; } /** * [used by spring] */ public void setLogVisibilityForCourseAuthor(Map<String, String> logVisibilities) { this.logVisibilities = logVisibilities; } @Override protected void initDefaultProperties() { courseChatEnabled = getBooleanConfigParameter("enableCourseChat", true); deployCoursesEnabled = getBooleanConfigParameter("deployCourseExportsEnabled", true); displayParticipantsCount = getBooleanConfigParameter("displayParticipantsCount", true); helpCourseSoftkey = getStringConfigParameter("helpCourseSoftKey", "", true); } /** * @see org.olat.core.configuration.OLATModule#init(com.anthonyeden.lib.config.Configuration) */ @Override public void init() { // skip all the expensive course demo setup and deployment when we are in junit mode. if (Settings.isJUnitTest()) return; logInfo("Initializing the OpenOLAT course system"); // Cleanup, otherwise this subjects will have problems in normal OLAT // operation DBFactory.getInstance(false).intermediateCommit(); } private void deployCoursesFromCourseExportFiles( ) { logInfo("Deploying course exports."); for (DeployableCourseExport export: deployableCourseExports) { if (0 < export.getAccess() && export.getAccess() < 5) { if (deployCoursesEnabled) { try { deployCourse(export, export.getAccess()); } catch (Exception e) { logWarn("Skipping deployment of course::" + export.getIdentifier(), e); } DBFactory.getInstance().intermediateCommit(); continue; } } else { logInfo("Skipping deployment of course::" + export.getIdentifier() + " ; access attribute must be 1,2,3 or 4 but values is::"+ export.getAccess()); } logInfo("Skipping deployment of course::" + export.getIdentifier()); } if (!deployCoursesEnabled) { logInfo("Skipping deployment of demo course exports. To deploy course exports, please enable in the configuration file. Help course will always be deployed!"); } } private RepositoryEntry deployCourse(DeployableCourseExport export, int access) { // let's see if we previously deployed demo courses... RepositoryEntry re = getDeployedCourses().get(export.getIdentifier()); if (re != null) { logInfo("Course '" + export.getIdentifier() + "' has been previousely deployed. Skipping."); return re; } File file = export.getDeployableCourseZipFile(); if (file != null && file.exists()) { logInfo("deploying Course: " + file.getName()); if (!file.exists()) { //do not throw exception as users may upload bad file logWarn("Cannot deploy course from file: " + file.getAbsolutePath(),null); return null; } re = CourseFactory.deployCourseFromZIP(file, null, access); if (re != null) markAsDeployed(export, re); return re; } return null; } /** * Mark a course as deployed. Remember the key of the repository entry it was * deployed. * * @param courseExportPath * @param re */ private void markAsDeployed(DeployableCourseExport export, RepositoryEntry re) { List<Property> props = propertyManager.findProperties(null, null, null, "_o3_", "deployedCourses"); Property prop = null; for (Property property : props) { if (property.getLongValue() == re.getKey()){ prop = property; } } if (prop == null) { prop = propertyManager.createPropertyInstance(null, null, null, "_o3_", "deployedCourses", export.getVersion(), re.getKey(), export.getIdentifier(), null); } prop.setFloatValue(export.getVersion()); prop.setStringValue(export.getIdentifier()); propertyManager.saveProperty(prop); deployedCourses.put(export.getIdentifier(), re); } /** * Get the Map of deployed courses. Map contains repo entries by path keys. * * @return */ private Map<String, RepositoryEntry> getDeployedCourses() { if (deployedCourses != null) return deployedCourses; List<?> props = propertyManager.findProperties(null, null, null, "_o3_", "deployedCourses"); deployedCourses = new HashMap<String, RepositoryEntry>(props.size()); for (Iterator<?> iter = props.iterator(); iter.hasNext();) { Property prop = (Property) iter.next(); Long repoKey = prop.getLongValue(); RepositoryEntry re = null; re = RepositoryManager.getInstance().lookupRepositoryEntry(repoKey); if (re != null) { //props with floatValue null are old entries - delete them. if (prop.getFloatValue() == null) { //those are courses deployed with the old mechanism, check, if they exist and what should be done with them: //fxdiff: no delete! logInfo("This course was already deployed and has old property values. course: "+prop.getStringValue()); for (DeployableCourseExport export: deployableCourseExports) { if (export.getIdentifier().equals(prop.getStringValue())) { logInfo("found this old course in the deployable courses list"); if (export.isRedeploy()){ // found in deployableCourses again and it should be redeployed, therefore delete: logInfo("marked as to be redeployed, therefore delete first!"); deleteCourseAndProperty(prop, re); re = null; //do not add to deployed courses } else { logInfo("no redeploy! just update its version."); markAsDeployed(export, re); } } } } else { //check if latest version if course is installed for (DeployableCourseExport export: deployableCourseExports) { if (export.getIdentifier().equals(prop.getStringValue()) && export.getVersion() > prop.getFloatValue() && export.isRedeploy()) { //we have a newer version - delete the old course logInfo("There is a new version for this course available. Deleting it and redeploy course: "+prop.getStringValue()); deleteCourseAndProperty(prop, re); re = null; //do not add to deployed courses break; } } } } if (re != null) deployedCourses.put(prop.getStringValue(), re); } return deployedCourses; } private void deleteCourseAndProperty(Property prop, RepositoryEntry re) { try { propertyManager.deleteProperty(prop); repositoryService.deleteRepositoryEntryAndBaseGroups(re); CourseFactory.deleteCourse(re.getOlatResource()); OLATResource ores = olatResourceManager.findResourceable(re.getOlatResource()); olatResourceManager.deleteOLATResource(ores); } catch (Exception e) { logWarn("Could not delete course and property of demo course with name: "+prop.getStringValue(), e); } } /** * @return true if the course author can see/download/modify the admin log */ public boolean isAdminLogVisibleForMigrationOnly() { return logVisibilities.get("AdminLog").equals("VISIBLE"); } /** * @return true if the course author can see/download/modify the user log */ public boolean isUserLogVisibleForMigrationOnly() { return logVisibilities.get("UserLog").equals("VISIBLE"); } /** * @return true if the course author can see/download/modify the statistic log */ public boolean isStatisticLogVisibleForMigrationOnly() { return logVisibilities.get("StatisticLog").equals("VISIBLE"); } /** * * @return The filename of the zipped help course */ public static String getHelpCourseSoftKey() { return helpCourseSoftkey; } /** * @return type name */ public static String getCourseTypeName() { return ORES_TYPE_COURSE; } /** * @param ce * @param cn * @return the generated SubscriptionContext */ public static SubscriptionContext createSubscriptionContext(CourseEnvironment ce, CourseNode cn) { SubscriptionContext sc = new SubscriptionContext(getCourseTypeName(), ce.getCourseResourceableId(), cn.getIdent()); return sc; } /** * @param ce * @param cn * @return a subscriptioncontext with no translations for the user, but only * to be able to cleanup/obtain */ public static SubscriptionContext createTechnicalSubscriptionContext(CourseEnvironment ce, CourseNode cn) { SubscriptionContext sc = new SubscriptionContext(getCourseTypeName(), ce.getCourseResourceableId(), cn.getIdent()); return sc; } /** * Creates subscription context which points to an element e.g. that is a sub * element of a node (subsubId). E.g. inside the course node dialog elements * where a course node can have several forums. * * @param ce * @param cn * @param subsubId * @return */ public static SubscriptionContext createSubscriptionContext(CourseEnvironment ce, CourseNode cn, String subsubId) { SubscriptionContext sc = new SubscriptionContext(getCourseTypeName(), ce.getCourseResourceableId(), cn.getIdent() + ":" + subsubId); return sc; } /** * whether course chat is enabled or not - depends on Instant Messaging enabled! you should check first for * IM Enabled @see {@link org.olat.instantMessaging.InstantMessagingModule} * @return */ public static boolean isCourseChatEnabled(){ return courseChatEnabled; } public static void registerForCourseType(GenericEventListener gel, Identity identity) { coordinatorManager.getCoordinator().getEventBus().registerFor(gel, identity, ORESOURCEABLE_TYPE_COURSE); } public static void deregisterForCourseType(GenericEventListener gel) { coordinatorManager.getCoordinator().getEventBus().deregisterFor(gel, ORESOURCEABLE_TYPE_COURSE); } /** * max number of course nodes * @return */ public static int getCourseNodeLimit() { return 499; } @Override protected void initFromChangedProperties() { // } @Override public void setPersistedProperties(PersistedProperties persistedProperties) { this.moduleConfigProperties = persistedProperties; } public static boolean displayParticipantsCount() { return CourseModule.displayParticipantsCount; } }