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.nodes;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.components.stack.StackedController;
import org.olat.core.gui.control.Controller;
import org.olat.core.gui.control.WindowControl;
import org.olat.core.gui.control.generic.messages.MessageUIFactory;
import org.olat.core.gui.control.generic.tabbable.TabbableController;
import org.olat.core.gui.translator.Translator;
import org.olat.core.util.CodeHelper;
import org.olat.core.util.StringHelper;
import org.olat.core.util.Util;
import org.olat.core.util.nodes.GenericNode;
import org.olat.core.util.xml.XStreamHelper;
import org.olat.course.ICourse;
import org.olat.course.condition.Condition;
import org.olat.course.condition.additionalconditions.AdditionalCondition;
import org.olat.course.condition.interpreter.ConditionErrorMessage;
import org.olat.course.condition.interpreter.ConditionExpression;
import org.olat.course.condition.interpreter.ConditionInterpreter;
import org.olat.course.editor.CourseEditorEnv;
import org.olat.course.editor.NodeConfigFormController;
import org.olat.course.editor.StatusDescription;
import org.olat.course.export.CourseEnvironmentMapper;
import org.olat.course.run.navigation.NodeRunConstructionResult;
import org.olat.course.run.userview.NodeEvaluation;
import org.olat.course.run.userview.TreeEvaluation;
import org.olat.course.run.userview.UserCourseEnvironment;
import org.olat.group.model.BGAreaReference;
import org.olat.group.model.BusinessGroupReference;
import org.olat.modules.ModuleConfiguration;
/**
* Description:<br>
* @author Felix Jost
* @author BPS (<a href="http://www.bps-system.de/">BPS Bildungsportal Sachsen GmbH</a>)
*/
public abstract class GenericCourseNode extends GenericNode implements CourseNode {
private static final long serialVersionUID = -1093400247219150363L;
private String type, shortTitle, longTitle, learningObjectives, displayOption;
private ModuleConfiguration moduleConfiguration;
private String noAccessExplanation;
private Condition preConditionVisibility;
private Condition preConditionAccess;
protected transient StatusDescription[] oneClickStatusCache = null;
protected List<AdditionalCondition> additionalConditions = new ArrayList<AdditionalCondition>();
/**
* Generic course node constructor
*
* @param type The course node type
*
* ATTENTION:
* all course nodes must call updateModuleConfigDefaults(true) here
*/
public GenericCourseNode(String type) {
super();
this.type = type;
moduleConfiguration = new ModuleConfiguration();
}
/**
* @see org.olat.course.nodes.CourseNode#createEditController(org.olat.core.gui.UserRequest,
* org.olat.core.gui.control.WindowControl, org.olat.course.ICourse)
*
* ATTENTION:
* all course nodes must call updateModuleConfigDefaults(false) here
*/
public abstract TabbableController createEditController(UserRequest ureq, WindowControl wControl, StackedController stackPanel, ICourse course,
* @see org.olat.course.nodes.CourseNode#createNodeRunConstructionResult(UserRequest,
* WindowControl, UserCourseEnvironment, NodeEvaluation, String)
*
* ATTENTION: all course nodes must call
* updateModuleConfigDefaults(false) here
public abstract NodeRunConstructionResult createNodeRunConstructionResult(
UserRequest ureq, WindowControl wControl,
UserCourseEnvironment userCourseEnv, NodeEvaluation ne,
String nodecmd);
protected String getDefaultTitleOption() {
return CourseNode.DISPLAY_OPTS_TITLE_DESCRIPTION_CONTENT;
}
/**
* Default implementation of the peekview controller that returns NULL: no
* node specific peekview information should be shown<br>
* Override this method with a specific implementation if you have
* something interesting to show in the peekview
*
* @see org.olat.course.nodes.CourseNode#createPeekViewRunController(UserRequest, WindowControl, UserCourseEnvironment, NodeEvaluation)
*/
public Controller createPeekViewRunController(UserRequest ureq, WindowControl wControl, UserCourseEnvironment userCourseEnv,
NodeEvaluation ne) {
return null;
}
/**
* default implementation of the previewController
*
* @see org.olat.course.nodes.CourseNode#createPreviewController(org.olat.core.gui.UserRequest,
* @see org.olat.core.gui.control.WindowControl,
* @see org.olat.course.run.userview.UserCourseEnvironment,
* @see org.olat.course.run.userview.NodeEvaluation)
*/
//no userCourseEnv or NodeEvaluation needed here
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
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
public Controller createPreviewController(UserRequest ureq, WindowControl wControl, UserCourseEnvironment userCourseEnv, NodeEvaluation ne) {
Translator translator = Util.createPackageTranslator(GenericCourseNode.class, ureq.getLocale());
String text = translator.translate("preview.notavailable");
return MessageUIFactory.createInfoMessage(ureq, wControl, null, text);
}
/**
* @return String
*/
public String getLearningObjectives() {
return learningObjectives;
}
/**
* @return String
*/
public String getLongTitle() {
return longTitle;
}
/**
* @return String
*/
public String getShortTitle() {
return shortTitle;
}
/**
* allows to specify if default value should be returned in case where there is no value.
* @param returnDefault if false: null may be returned if no value found!
* @return String
*/
public String getDisplayOption(boolean returnDefault) {
if(!StringHelper.containsNonWhitespace(displayOption) && returnDefault) {
return getDefaultTitleOption();
}
return displayOption;
}
/**
* @return String with the old behavior (default value if none existing)
*/
public String getDisplayOption() {
return getDisplayOption(true);
}
/**
* @return String
*/
public String getType() {
return type;
}
/**
* Sets the learningObjectives.
*
* @param learningObjectives The learningObjectives to set
*/
public void setLearningObjectives(String learningObjectives) {
this.learningObjectives = learningObjectives;
}
/**
* Sets the longTitle.
*
* @param longTitle The longTitle to set
*/
public void setLongTitle(String longTitle) {
this.longTitle = longTitle;
}
/**
* Sets the shortTitle.
*
* @param shortTitle The shortTitle to set
*/
public void setShortTitle(String shortTitle) {
this.shortTitle = shortTitle;
}
/**
* Sets the display option
* @param displayOption
*/
public void setDisplayOption(String displayOption) {
this.displayOption = displayOption;
}
/**
* Sets the type.
*
* @param type The type to set
*/
public void setType(String type) {
this.type = type;
}
/**
* @return ModuleConfiguration
*/
public ModuleConfiguration getModuleConfiguration() {
return moduleConfiguration;
}
/**
* Sets the moduleConfiguration.
*
* @param moduleConfiguration The moduleConfiguration to set
*/
public void setModuleConfiguration(ModuleConfiguration moduleConfiguration) {
this.moduleConfiguration = moduleConfiguration;
}
/**
* @see org.olat.course.nodes.CourseNode#eval(org.olat.course.condition.interpreter.ConditionInterpreter,
* org.olat.course.run.userview.TreeEvaluation)
*/
public NodeEvaluation eval(ConditionInterpreter ci, TreeEvaluation treeEval) {
// each CourseNodeImplementation has the full control over all children eval.
// default behaviour is to eval all visible children
NodeEvaluation nodeEval = new NodeEvaluation(this);
calcAccessAndVisibility(ci, nodeEval);
nodeEval.build();
treeEval.cacheCourseToTreeNode(this, nodeEval.getTreeNode());
// only add children (coursenodes/nodeeval) when I am visible and
// atleastOneAccessible myself
if (nodeEval.isVisible() && nodeEval.isAtLeastOneAccessible()) {
int childcnt = getChildCount();
for (int i = 0; i < childcnt; i++) {
CourseNode cn = (CourseNode)getChildAt(i);
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
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
NodeEvaluation chdEval = cn.eval(ci, treeEval);
if (chdEval.isVisible()) { // child is visible
nodeEval.addNodeEvaluationChild(chdEval);
}
}
}
return nodeEval;
}
/**
* @param ci the ConditionInterpreter as the calculating machine
* @param nodeEval the object to write the results into
*/
protected abstract void calcAccessAndVisibility(ConditionInterpreter ci, NodeEvaluation nodeEval);
/**
* @return String
*/
public String getNoAccessExplanation() {
return noAccessExplanation;
}
/**
* Sets the noAccessExplanation.
*
* @param noAccessExplanation The noAccessExplanation to set
*/
public void setNoAccessExplanation(String noAccessExplanation) {
this.noAccessExplanation = noAccessExplanation;
}
/**
* @return Condition
*/
public Condition getPreConditionVisibility() {
if (preConditionVisibility == null) {
preConditionVisibility = new Condition();
}
preConditionVisibility.setConditionId("visibility");
return preConditionVisibility;
}
/**
* Sets the preConditionVisibility.
*
* @param preConditionVisibility The preConditionVisibility to set
*/
public void setPreConditionVisibility(Condition preConditionVisibility) {
if (preConditionVisibility == null) {
preConditionVisibility = getPreConditionVisibility();
}
this.preConditionVisibility = preConditionVisibility;
this.preConditionVisibility.setConditionId("visibility");
}
/**
* @return Condition
*/
public Condition getPreConditionAccess() {
if (preConditionAccess == null) {
preConditionAccess = new Condition();
}
preConditionAccess.setConditionId("accessability");
return preConditionAccess;
}
/**
* Generic interface implementation. May be overriden by specific node's
* implementation.
*
* @see org.olat.course.nodes.CourseNode#informOnDelete(org.olat.core.gui.UserRequest,
* org.olat.course.ICourse)
*/
public String informOnDelete(Locale locale, ICourse course) {
return null;
}
/**
* Generic interface implementation. May be overriden by specific node's
* implementation.
*
* @see org.olat.course.nodes.CourseNode#cleanupOnDelete(org.olat.course.ICourse)
*/
public void cleanupOnDelete(ICourse course) {
/**
* do nothing in default implementation
*/
}
/**
* Generic interface implementation. May be overriden by specific node's
* implementation.
*
* @see org.olat.course.nodes.CourseNode#archiveNodeData(java.util.Locale,
* org.olat.course.ICourse, java.io.File)
*/
//implemented by specialized node
public boolean archiveNodeData(Locale locale, ICourse course, File exportDirectory, String charset) {
// nothing to do in default implementation
return true;
}
/**
* @see org.olat.course.nodes.CourseNode#exportNode(java.io.File,
* org.olat.course.ICourse)
*/
//implemented by specialized node
public void exportNode(File exportDirectory, ICourse course) {
// nothing to do in default implementation
}
/**
* Implemented by specialized node
* @see org.olat.course.nodes.CourseNode#importNode(java.io.File,
* org.olat.course.ICourse, org.olat.core.gui.UserRequest,
* org.olat.core.gui.control.WindowControl)
*/
public Controller importNode(File importDirectory, ICourse course, boolean unattendedImport, UserRequest ureq, WindowControl wControl) {
// nothing to do in default implementation
return null;
}
@Override
public void postImport(CourseEnvironmentMapper envMapper) {
postImportCondition(preConditionAccess, envMapper);
postImportCondition(preConditionVisibility, envMapper);
}
protected void postImportCondition(Condition condition, CourseEnvironmentMapper envMapper) {
if(condition == null) return;
if(condition.isExpertMode()) {
String expression = condition.getConditionExpression();
if(StringHelper.containsNonWhitespace(expression)) {
String processExpression = convertExpressionNameToKey(expression, envMapper);
processExpression = convertExpressionKeyToKey(processExpression, envMapper);
if(!expression.equals(processExpression)) {
condition.setConditionExpression(processExpression);
}
}
} else if(StringHelper.containsNonWhitespace(condition.getConditionFromEasyModeConfiguration())) {
List<Long> groupKeys = condition.getEasyModeGroupAccessIdList();

srosse
committed
if(groupKeys == null || groupKeys.isEmpty()) {
//this is an old course -> get group keys from original names
groupKeys = envMapper.toGroupKeyFromOriginalNames(condition.getEasyModeGroupAccess());
} else {
//map the original exported group key to the newly created one
groupKeys = envMapper.toGroupKeyFromOriginalKeys(groupKeys);
}

srosse
committed
condition.setEasyModeGroupAccessIdList(groupKeys);//update keys
condition.setEasyModeGroupAccess(envMapper.toGroupNames(groupKeys));//update names with the current values
List<Long> areaKeys = condition.getEasyModeGroupAreaAccessIdList();

srosse
committed
if(areaKeys == null || areaKeys.isEmpty()) {
areaKeys = envMapper.toAreaKeyFromOriginalNames(condition.getEasyModeGroupAreaAccess());
} else {
areaKeys = envMapper.toAreaKeyFromOriginalKeys(areaKeys);
}
condition.setEasyModeGroupAreaAccessIdList(areaKeys);

srosse
committed
condition.setEasyModeGroupAreaAccess(envMapper.toAreaNames(areaKeys));
String condString = condition.getConditionFromEasyModeConfiguration();
condition.setConditionExpression(condString);
}
}
@Override
public void postExport(CourseEnvironmentMapper envMapper, boolean backwardsCompatible) {
postExportCondition(preConditionAccess, envMapper, backwardsCompatible);
postExportCondition(preConditionVisibility, envMapper, backwardsCompatible);
}
protected void postExportCondition(Condition condition, CourseEnvironmentMapper envMapper, boolean backwardsCompatible) {
if(condition == null) return;
boolean easy = StringHelper.containsNonWhitespace(condition.getConditionFromEasyModeConfiguration());
if(easy) {
//already processed?
if(condition.getEasyModeGroupAccessIdList() != null
|| condition.getEasyModeGroupAreaAccessIdList() != null) {

srosse
committed
String groupNames = envMapper.toGroupNames(condition.getEasyModeGroupAccessIdList());
condition.setEasyModeGroupAccess(groupNames);

srosse
committed
String areaNames = envMapper.toAreaNames(condition.getEasyModeGroupAreaAccessIdList());
condition.setEasyModeGroupAreaAccess(areaNames);
String condString = condition.getConditionFromEasyModeConfiguration();
if(backwardsCompatible) {
condString = convertExpressionKeyToName(condString, envMapper);
}
condition.setConditionExpression(condString);
}
} else if(condition.isExpertMode() && backwardsCompatible) {
String expression = condition.getConditionExpression();
if(StringHelper.containsNonWhitespace(expression)) {
String processExpression = convertExpressionKeyToName(expression, envMapper);
if(!expression.equals(processExpression)) {
condition.setConditionExpression(processExpression);
}
}
}
if(backwardsCompatible) {
condition.setEasyModeGroupAreaAccessIds(null);
condition.setEasyModeGroupAccessIds(null);
//condition.setConditionUpgraded(null);
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
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
}
}
protected String convertExpressionKeyToName(String expression, CourseEnvironmentMapper envMapper) {
for(BusinessGroupReference group:envMapper.getGroups()) {
String strToMatch = "\"" + group.getKey() + "\"";
String replacement = "\"" + group.getName() + "\"";
expression = StringHelper.replaceAllCaseInsensitive(expression, strToMatch, replacement);
}
for(BGAreaReference area:envMapper.getAreas()) {
String strToMatch = "\"" + area.getKey() + "\"";
String replacement = "\"" + area.getName() + "\"";
expression = StringHelper.replaceAllCaseInsensitive(expression, strToMatch, replacement);
}
return expression;
}
protected String convertExpressionNameToKey(String expression, CourseEnvironmentMapper envMapper) {
for(BusinessGroupReference group:envMapper.getGroups()) {
String strToMatch = "\"" + group.getOriginalName() + "\"";
String replacement = "\"" + group.getKey() + "\"";
expression = StringHelper.replaceAllCaseInsensitive(expression, strToMatch, replacement);
}
for(BGAreaReference area:envMapper.getAreas()) {
String strToMatch = "\"" + area.getOriginalName() + "\"";
String replacement = "\"" + area.getKey() + "\"";
expression = StringHelper.replaceAllCaseInsensitive(expression, strToMatch, replacement);
}
return expression;
}
protected String convertExpressionKeyToKey(String expression, CourseEnvironmentMapper envMapper) {
for(BusinessGroupReference group:envMapper.getGroups()) {
String strToMatch = "\"" + group.getOriginalKey() + "\"";
String replacement = "\"" + group.getKey() + "\"";
expression = StringHelper.replaceAllCaseInsensitive(expression, strToMatch, replacement);
}
for(BGAreaReference area:envMapper.getAreas()) {
String strToMatch = "\"" + area.getOriginalKey() + "\"";
String replacement = "\"" + area.getKey() + "\"";
expression = StringHelper.replaceAllCaseInsensitive(expression, strToMatch, replacement);
}
return expression;
}
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
/**
* @see org.olat.core.gui.ShortName#getShortName()
*/
public String getShortName() {
return getShortTitle();
}
/**
* @see org.olat.course.nodes.CourseNode#createInstanceForCopy()
*/
public CourseNode createInstanceForCopy() {
return createInstanceForCopy(true);
}
public CourseNode createInstanceForCopy(boolean isNewTitle) {
CourseNode copyInstance = (CourseNode) XStreamHelper.xstreamClone(this);
copyInstance.setIdent(String.valueOf(CodeHelper.getForeverUniqueID()));
copyInstance.setPreConditionVisibility(null);
if (isNewTitle) {
// FIXME:pb:ms translation for COPY OF
String newTitle = "Copy of " + getShortTitle();
if (newTitle.length() > NodeConfigFormController.SHORT_TITLE_MAX_LENGTH) newTitle = newTitle.substring(0,
NodeConfigFormController.SHORT_TITLE_MAX_LENGTH - 1);
copyInstance.setShortTitle(newTitle);
}
return copyInstance;
}
@Override
public void copyConfigurationTo(CourseNode courseNode) {
if(courseNode instanceof GenericCourseNode) {
GenericCourseNode newNode = (GenericCourseNode)courseNode;
newNode.setDisplayOption(getDisplayOption());
newNode.setLearningObjectives(getLearningObjectives());
newNode.setLongTitle(getLongTitle());
newNode.setNoAccessExplanation(getNoAccessExplanation());
newNode.setShortTitle(getShortTitle());
if(preConditionVisibility != null) {
newNode.setPreConditionVisibility(preConditionVisibility.clone());
}
}
}
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
598
599
600
/**
* @see java.lang.Object#toString()
*/
public String toString() {
return "Id: " + getIdent() + ", '" + getShortTitle() + "' " + super.toString();
}
/**
* @see org.olat.course.nodes.CourseNode#getConditionExpressions()
*/
public List<ConditionExpression> getConditionExpressions() {
ArrayList<ConditionExpression> retVal = new ArrayList<ConditionExpression>();
String coS = getPreConditionVisibility().getConditionExpression();
if (coS != null && !coS.equals("")) {
// an active condition is defined
ConditionExpression ce = new ConditionExpression(getPreConditionVisibility().getConditionId());
ce.setExpressionString(getPreConditionVisibility().getConditionExpression());
retVal.add(ce);
}
//
return retVal;
}
/**
* must be implemented in the concrete subclasses as a translator is needed
* for the errormessages which comes with evaluating condition expressions
*
* @see org.olat.course.nodes.CourseNode#isConfigValid(org.olat.course.run.userview.UserCourseEnvironment)
*/
public abstract StatusDescription[] isConfigValid(CourseEditorEnv cev);
/**
* @param userCourseEnv
* @param translatorStr
* @return
*/
//for StatusDescription.WARNING
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
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
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
protected List<StatusDescription> isConfigValidWithTranslator(CourseEditorEnv cev, String translatorStr, List<ConditionExpression> condExprs) {
List<StatusDescription> condExprsStatusDescs = new ArrayList<StatusDescription>();
// check valid configuration without course environment
StatusDescription first = isConfigValid();
// check valid configuration within the course environment
if (cev == null) {
// course environment not configured!??
condExprsStatusDescs.add(first);
return condExprsStatusDescs;
}
/*
* there is course editor environment, we can check further. Iterate over
* all conditions of this course node, validate the condition expression and
* transform the condition error message into a status description
*/
for (int i = 0; i < condExprs.size(); i++) {
ConditionExpression ce = condExprs.get(i);
ConditionErrorMessage[] cems = cev.validateConditionExpression(ce);
if (cems != null && cems.length > 0) {
for (int j = 0; j < cems.length; j++) {
StatusDescription sd = new StatusDescription(StatusDescription.WARNING, cems[j].errorKey, cems[j].solutionMsgKey,
cems[j].errorKeyParams, translatorStr);
sd.setDescriptionForUnit(getIdent());
condExprsStatusDescs.add(sd);
}
}
}
condExprsStatusDescs.add(first);
return condExprsStatusDescs;
}
/**
* @see org.olat.course.nodes.CourseNode#explainThisDuringPublish(org.olat.core.gui.control.StatusDescription)
*/
public StatusDescription explainThisDuringPublish(StatusDescription description) {
if (description == null) return null;
StatusDescription retVal = null;
if (description.getShortDescriptionKey().equals("error.notfound.coursenodeid")) {
retVal = description.transformTo("error.notfound.coursenodeid.publish", "error.notfound.coursenodeid.publish", null);
} else if (description.getShortDescriptionKey().equals("error.notfound.name")) {
retVal = description.transformTo("error.notfound.name.publish", "error.notfound.name.publish", null);
} else if (description.getShortDescriptionKey().equals("error.notassessable.coursenodid")) {
retVal = description.transformTo("error.notassessable.coursenodid.publish", "error.notassessable.coursenodid.publish", null);
} else {
// throw new OLATRuntimeException("node does not know how to translate <b
// style='color:red'>" + description.getShortDescriptionKey()
// + "</b> in publish env", new IllegalArgumentException());
return description;
}
return retVal;
}
/**
* Update the module configuration to have all mandatory configuration flags
* set to usefull default values
*
* @param isNewNode true: an initial configuration is set; false: upgrading
* from previous node configuration version, set default to maintain
* previous behaviour
*
* This is the workflow:
* On every click on a entry of the navigation tree, this method will be called
* to ensure a valid configration of the depending module. This is only done in
* RAM. If the user clicks on that node in course editor and publishes the course
* after that, then the updated config will be persisted to disk. Otherwise
* everything what is done here has to be done once at every course start.
*/
//implemented by specialized node
public void updateModuleConfigDefaults(boolean isNewNode) {
/**
* Do NO updating here, since this method can be overwritten by all classes
* implementing this. This is only implemented here to avoid changing all
* couseNode classes which do not implement this method.
*/
}
}