/** * 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.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import org.apache.commons.codec.binary.Base64; import org.apache.commons.io.IOUtils; import org.olat.core.CoreSpringFactory; import org.olat.core.commons.modules.bc.FolderConfig; import org.olat.core.commons.modules.bc.vfs.OlatRootFolderImpl; import org.olat.core.commons.services.taskexecutor.TaskExecutorManager; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.stack.BreadcrumbPanel; import org.olat.core.gui.components.stack.TooledStackedPanel; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.generic.messages.MessageUIFactory; import org.olat.core.gui.control.generic.tabbable.TabbableController; import org.olat.core.gui.translator.PackageTranslator; import org.olat.core.gui.translator.Translator; import org.olat.core.id.Identity; import org.olat.core.id.Roles; import org.olat.core.id.context.BusinessControl; import org.olat.core.id.context.BusinessControlFactory; 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.util.DirectoryFilter; import org.olat.core.util.ExportUtil; import org.olat.core.util.FileNameSuffixFilter; import org.olat.core.util.FileUtils; import org.olat.core.util.Formatter; import org.olat.core.util.Util; import org.olat.core.util.ZipUtil; import org.olat.core.util.vfs.LocalFileImpl; import org.olat.core.util.vfs.VFSContainer; import org.olat.core.util.vfs.VFSItem; import org.olat.core.util.vfs.VFSLeaf; import org.olat.core.util.vfs.VFSManager; import org.olat.core.util.xml.XStreamHelper; import org.olat.course.ICourse; import org.olat.course.assessment.AssessmentManager; import org.olat.course.assessment.bulk.BulkAssessmentToolController; import org.olat.course.auditing.UserNodeAuditManager; import org.olat.course.condition.Condition; 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.NodeEditController; import org.olat.course.editor.StatusDescription; import org.olat.course.export.CourseEnvironmentMapper; import org.olat.course.nodes.ms.MSEditFormController; import org.olat.course.nodes.projectbroker.ProjectBrokerControllerFactory; import org.olat.course.nodes.projectbroker.ProjectBrokerCourseEditorController; import org.olat.course.nodes.projectbroker.ProjectListController; import org.olat.course.nodes.projectbroker.datamodel.Project; import org.olat.course.nodes.projectbroker.datamodel.ProjectBroker; import org.olat.course.nodes.projectbroker.service.ProjectBrokerExportGenerator; import org.olat.course.nodes.projectbroker.service.ProjectBrokerManager; import org.olat.course.nodes.projectbroker.service.ProjectGroupManager; import org.olat.course.nodes.ta.DropboxController; import org.olat.course.nodes.ta.ReturnboxController; import org.olat.course.nodes.ta.TaskController; import org.olat.course.properties.CoursePropertyManager; import org.olat.course.properties.PersistingCoursePropertyManager; import org.olat.course.run.environment.CourseEnvironment; import org.olat.course.run.navigation.NodeRunConstructionResult; import org.olat.course.run.scoring.AssessmentEvaluation; import org.olat.course.run.scoring.ScoreEvaluation; import org.olat.course.run.userview.NodeEvaluation; import org.olat.course.run.userview.UserCourseEnvironment; import org.olat.group.BusinessGroup; import org.olat.group.BusinessGroupService; import org.olat.group.model.BusinessGroupReference; import org.olat.modules.ModuleConfiguration; import org.olat.modules.assessment.AssessmentEntry; import org.olat.modules.assessment.AssessmentToolOptions; import org.olat.properties.Property; import org.olat.repository.RepositoryEntry; import org.olat.resource.OLATResource; import com.thoughtworks.xstream.XStream; /** * * @author Christian Guretzki */ public class ProjectBrokerCourseNode extends GenericCourseNode implements PersistentAssessableCourseNode { private static final long serialVersionUID = -8177448874150049173L; private static final OLog log = Tracing.createLoggerFor(ProjectBrokerCourseNode.class); private transient static final String PACKAGE_PROJECTBROKER = Util.getPackageName(ProjectListController.class); private transient static final String PACKAGE = Util.getPackageName(ProjectBrokerCourseNode.class); private transient static final String TYPE = "projectbroker"; // NLS support: private transient static final String NLS_GUESTNOACCESS_TITLE = "guestnoaccess.title"; private transient static final String NLS_GUESTNOACCESS_MESSAGE = "guestnoaccess.message"; private transient static final String NLS_ERROR_MISSINGSCORECONFIG_SHORT = "error.missingscoreconfig.short"; private transient static final String NLS_WARN_NODEDELETE = "warn.nodedelete"; // MUST BE NON TRANSIENT private static final int CURRENT_CONFIG_VERSION = 2; /** CONF_DROPBOX_ENABLED configuration parameter key. */ public transient static final String CONF_DROPBOX_ENABLED = "dropbox_enabled"; /** CONF_DROPBOX_ENABLEMAIL configuration parameter key. */ public transient static final String CONF_DROPBOX_ENABLEMAIL = "dropbox_enablemail"; /** CONF_DROPBOX_CONFIRMATION configuration parameter key. */ public transient static final String CONF_DROPBOX_CONFIRMATION = "dropbox_confirmation"; /** CONF_SCORING_ENABLED configuration parameter key. */ public transient static final String CONF_SCORING_ENABLED = "scoring_enabled"; /** ACCESS_SCORING configuration parameter key. */ public transient static final String ACCESS_SCORING = "scoring"; /** ACCESS_DROPBOX configuration parameter key. */ public transient static final String ACCESS_DROPBOX = "dropbox"; public transient static final String ACCESS_RETURNBOX = "returnbox"; public transient static final String ACCESS_PROJECTBROKER = "projectbroker"; /** CONF_TASK_PREVIEW configuration parameter key used for task-form. */ public transient static final String CONF_TASK_PREVIEW = "task_preview"; public transient static final String CONF_RETURNBOX_ENABLED = "returnbox_enabled"; public transient static final String CONF_ACCOUNTMANAGER_GROUP_KEY = "config_accountmanager_group_id"; public transient static final String CONF_PROJECTBROKER_KEY = "conf_projectbroker_id"; public transient static final String CONF_NODE_SHORT_TITLE_KEY = "conf_node_short_title"; // MUST BE NON TRANSIENT private Condition conditionDrop, conditionScoring, conditionReturnbox; private Condition conditionProjectBroker; /** * Default constructor. */ public ProjectBrokerCourseNode() { super(TYPE); updateModuleConfigDefaults(true); } /** * @see org.olat.course.nodes.CourseNode#createEditController(org.olat.core.gui.UserRequest, * org.olat.core.gui.control.WindowControl, org.olat.course.ICourse) */ @Override public TabbableController createEditController(UserRequest ureq, WindowControl wControl, BreadcrumbPanel stackPanel, ICourse course, UserCourseEnvironment euce) { updateModuleConfigDefaults(false); ProjectBrokerCourseEditorController childTabCntrllr = ProjectBrokerControllerFactory.createCourseEditController(ureq, wControl, course, euce, this ); CourseNode chosenNode = course.getEditorTreeModel().getCourseNode(euce.getCourseEditorEnv().getCurrentCourseNodeId()); NodeEditController editController = new NodeEditController(ureq, wControl, course.getEditorTreeModel(), course, chosenNode, euce, childTabCntrllr); editController.addControllerListener(childTabCntrllr); return editController; } /** * @see org.olat.course.nodes.CourseNode#createNodeRunConstructionResult(org.olat.core.gui.UserRequest, * org.olat.core.gui.control.WindowControl, * org.olat.course.run.userview.UserCourseEnvironment, * org.olat.course.run.userview.NodeEvaluation) */ @Override public NodeRunConstructionResult createNodeRunConstructionResult(UserRequest ureq, WindowControl wControl, UserCourseEnvironment userCourseEnv, NodeEvaluation ne, String nodecmd) { updateModuleConfigDefaults(false); Controller controller; // Do not allow guests to access tasks Roles roles = ureq.getUserSession().getRoles(); if (roles.isGuestOnly()) { Translator trans = new PackageTranslator(PACKAGE, ureq.getLocale()); String title = trans.translate(NLS_GUESTNOACCESS_TITLE); String message = trans.translate(NLS_GUESTNOACCESS_MESSAGE); controller = MessageUIFactory.createInfoMessage(ureq, wControl, title, message); } else { // Add message id to business path if nodemcd is available if (nodecmd != null) { try { Long projectId = Long.valueOf(nodecmd); BusinessControlFactory bcf = BusinessControlFactory.getInstance(); BusinessControl businessControl = bcf.createFromString("[Project:"+projectId+"]"); wControl = bcf.createBusinessWindowControl(businessControl, wControl); } catch (NumberFormatException e) { // ups, nodecmd is not a message, what the heck is it then? log.warn("Could not create message ID from given nodemcd::" + nodecmd, e); } } controller = ProjectBrokerControllerFactory.createRunController(ureq, wControl,userCourseEnv, this); } Controller wrapperCtrl = TitledWrapperHelper.getWrapper(ureq, wControl, controller, this, "o_projectbroker_icon"); return new NodeRunConstructionResult(wrapperCtrl); } /** * @see org.olat.course.nodes.GenericCourseNode#createPreviewController(org.olat.core.gui.UserRequest, * org.olat.core.gui.control.WindowControl, * org.olat.course.run.userview.UserCourseEnvironment, * org.olat.course.run.userview.NodeEvaluation) */ @Override public Controller createPreviewController(UserRequest ureq, WindowControl wControl, UserCourseEnvironment userCourseEnv, NodeEvaluation ne) { return ProjectBrokerControllerFactory.createPreviewController(ureq, wControl,userCourseEnv, this); } /** * @see org.olat.course.nodes.GenericCourseNode#createPeekViewRunController(org.olat.core.gui.UserRequest, * org.olat.core.gui.control.WindowControl, * org.olat.course.run.userview.UserCourseEnvironment, * org.olat.course.run.userview.NodeEvaluation) */ @Override public Controller createPeekViewRunController(UserRequest ureq, WindowControl wControl, UserCourseEnvironment userCourseEnv, NodeEvaluation ne) { if (ne.isAtLeastOneAccessible()) { Controller peekViewController = ProjectBrokerControllerFactory.createPeekViewRunController(ureq, wControl, userCourseEnv, this); return peekViewController; } else { // use standard peekview return super.createPeekViewRunController(ureq, wControl, userCourseEnv, ne); } } /** * @see org.olat.course.nodes.CourseNode#getReferencedRepositoryEntry() */ public RepositoryEntry getReferencedRepositoryEntry() { return null; } /** * @see org.olat.course.nodes.CourseNode#needsReferenceToARepositoryEntry() */ public boolean needsReferenceToARepositoryEntry() { return false; } /** * @see org.olat.course.nodes.CourseNode#isConfigValid() */ public StatusDescription isConfigValid() { /* * first check the one click cache */ if (oneClickStatusCache != null) { return oneClickStatusCache[0]; } boolean isValid = true; Boolean hasScoring = (Boolean) getModuleConfiguration().get(CONF_SCORING_ENABLED); if (hasScoring.booleanValue()) { if (!MSEditFormController.isConfigValid(getModuleConfiguration())) isValid = false; } StatusDescription sd = StatusDescription.NOERROR; if (!isValid) { String shortKey = NLS_ERROR_MISSINGSCORECONFIG_SHORT; String longKey = NLS_ERROR_MISSINGSCORECONFIG_SHORT; String[] params = new String[] { this.getShortTitle() }; String translPackage = Util.getPackageName(MSEditFormController.class); sd = new StatusDescription(StatusDescription.ERROR, shortKey, longKey, params, translPackage); sd.setDescriptionForUnit(getIdent()); // set which pane is affected by error } return sd; } /** * @see org.olat.course.nodes.CourseNode#isConfigValid(org.olat.course.run.userview.UserCourseEnvironment) */ @Override public StatusDescription[] isConfigValid(CourseEditorEnv cev) { oneClickStatusCache = null; // only here we know which translator to take for translating condition error messages // check if group-manager is already initialized List<StatusDescription> sds = isConfigValidWithTranslator(cev, PACKAGE_PROJECTBROKER, getConditionExpressions()); oneClickStatusCache = StatusDescriptionHelper.sort(sds); return oneClickStatusCache; } @Override protected void calcAccessAndVisibility(ConditionInterpreter ci, NodeEvaluation nodeEval) { if (ci == null) throw new OLATRuntimeException("no condition interpreter <" + getIdent() + " " + getShortName() + ">", new IllegalArgumentException()); if (nodeEval == null) throw new OLATRuntimeException("node Evaluationt is null!! for <" + getIdent() + " " + getShortName() + ">", new IllegalArgumentException()); // evaluate the preconditions boolean projectBrokerAccess = (getConditionProjectBroker().getConditionExpression() == null ? true : ci.evaluateCondition(conditionProjectBroker)); nodeEval.putAccessStatus(ACCESS_PROJECTBROKER, projectBrokerAccess); // add a dummy access-status to open course node in general otherwise the hole project-broker could be closed boolean visible = (getPreConditionVisibility().getConditionExpression() == null ? true : ci .evaluateCondition(getPreConditionVisibility())); nodeEval.setVisible(visible); } /** * @see org.olat.course.nodes.CourseNode#informOnDelete(org.olat.core.gui.UserRequest, * org.olat.course.ICourse) */ @Override public String informOnDelete(Locale locale, ICourse course) { Translator trans = new PackageTranslator(PACKAGE_PROJECTBROKER, locale); CoursePropertyManager cpm = PersistingCoursePropertyManager.getInstance(course); List<Property> list = cpm.listCourseNodeProperties(this, null, null, null); if (list.size() != 0) return trans.translate(NLS_WARN_NODEDELETE); // properties exist File fDropboxFolder = new File(FolderConfig.getCanonicalRoot() + DropboxController.getDropboxPathRelToFolderRoot(course.getCourseEnvironment(), this)); if (fDropboxFolder.exists() && fDropboxFolder.list().length > 0) return trans.translate(NLS_WARN_NODEDELETE); // Dropbox folder contains files File fReturnboxFolder = new File(FolderConfig.getCanonicalRoot() + ReturnboxController.getReturnboxPathRelToFolderRoot(course.getCourseEnvironment(), this)); if (fReturnboxFolder.exists() && fReturnboxFolder.list().length > 0) return trans.translate(NLS_WARN_NODEDELETE); // Returnbox folder contains files return null; // no data yet. } /** * @see org.olat.course.nodes.CourseNode#cleanupOnDelete( * org.olat.course.ICourse) */ @Override public void cleanupOnDelete(ICourse course) { CoursePropertyManager cpm = course.getCourseEnvironment().getCoursePropertyManager(); ProjectBrokerManager projectBrokerManager = CoreSpringFactory.getImpl(ProjectBrokerManager.class); Long projectBrokerId = projectBrokerManager.getProjectBrokerId(cpm, this); File fDropBox = new File(FolderConfig.getCanonicalRoot() + DropboxController.getDropboxPathRelToFolderRoot(course.getCourseEnvironment(), this)); if (fDropBox.exists()) { FileUtils.deleteDirsAndFiles(fDropBox, true, true); } File fReturnBox = new File(FolderConfig.getCanonicalRoot() + ReturnboxController.getReturnboxPathRelToFolderRoot(course.getCourseEnvironment(), this)); if (fReturnBox.exists()) { FileUtils.deleteDirsAndFiles(fReturnBox, true, true); } File attachmentDir = new File(FolderConfig.getCanonicalRoot() + projectBrokerManager.getAttachmentBasePathRelToFolderRoot(course.getCourseEnvironment(), this)); if (attachmentDir.exists()) { FileUtils.deleteDirsAndFiles(attachmentDir, true, true); } // Delete project-broker, projects and project-groups if (projectBrokerId != null) { projectBrokerManager.deleteProjectBroker(projectBrokerId, course.getCourseEnvironment(), this); } // Delete all properties... cpm.deleteNodeProperties(this, null); OLATResource resource = course.getCourseEnvironment().getCourseGroupManager().getCourseResource(); CoreSpringFactory.getImpl(TaskExecutorManager.class).delete(resource, getIdent()); } /** * @return dropbox condition */ public Condition getConditionDrop() { if (conditionDrop == null) { conditionDrop = new Condition(); } conditionDrop.setConditionId("drop"); return conditionDrop; } /** * @return scoring condition */ public Condition getConditionScoring() { if (conditionScoring == null) { conditionScoring = new Condition(); } conditionScoring.setConditionId("scoring"); return conditionScoring; } /** * * @return Returnbox condition */ public Condition getConditionReturnbox() { if (conditionReturnbox == null) { conditionReturnbox = new Condition(); } conditionReturnbox.setConditionId("returnbox"); return conditionReturnbox; } /** * @param conditionDrop */ public void setConditionDrop(Condition conditionDrop) { if (conditionDrop == null) { conditionDrop = getConditionDrop(); } conditionDrop.setConditionId("drop"); this.conditionDrop = conditionDrop; } /** * @param conditionScoring */ public void setConditionScoring(Condition conditionScoring) { if (conditionScoring == null) { conditionScoring = getConditionScoring(); } conditionScoring.setConditionId("scoring"); this.conditionScoring = conditionScoring; } /** * * @param condition */ public void setConditionReturnbox(Condition condition) { if (condition == null) { condition = getConditionReturnbox(); } condition.setConditionId("returnbox"); this.conditionReturnbox = condition; } public Condition getConditionProjectBroker() { if (conditionProjectBroker == null) { conditionProjectBroker = new Condition(); } conditionProjectBroker.setConditionId("projectbroker"); return conditionProjectBroker; } public void setConditionProjectBroker(Condition condition) { if (condition == null) { condition = getConditionProjectBroker(); } condition.setConditionId("projectbroker"); this.conditionProjectBroker = condition; } @Override public AssessmentEvaluation getUserScoreEvaluation(UserCourseEnvironment userCourseEnv) { return getUserScoreEvaluation(getUserAssessmentEntry(userCourseEnv)); } @Override public AssessmentEvaluation getUserScoreEvaluation(AssessmentEntry entry) { return AssessmentEvaluation.toAssessmentEvalutation(entry, this); } @Override public AssessmentEntry getUserAssessmentEntry(UserCourseEnvironment userCourseEnv) { AssessmentManager am = userCourseEnv.getCourseEnvironment().getAssessmentManager(); Identity mySelf = userCourseEnv.getIdentityEnvironment().getIdentity(); return am.getAssessmentEntry(this, mySelf); } @Override public boolean isAssessedBusinessGroups() { return false; } /** * @see org.olat.course.nodes.AssessableCourseNode#hasCommentConfigured() */ @Override public boolean hasCommentConfigured() { return false; } /** * @see org.olat.course.nodes.AssessableCourseNode#hasPassedConfigured() */ @Override public boolean hasPassedConfigured() { return false; } /** * @see org.olat.course.nodes.AssessableCourseNode#hasScoreConfigured() */ @Override public boolean hasScoreConfigured() { return false; } /** * @see org.olat.course.nodes.AssessableCourseNode#hasStatusConfigured() */ @Override public boolean hasStatusConfigured() { return false; // Project broker Course node has no status-field } /** * @see org.olat.course.nodes.AssessableCourseNode#getMaxScoreConfiguration() */ @Override public Float getMaxScoreConfiguration() { if (!hasScoreConfigured()) { throw new OLATRuntimeException(ProjectBrokerCourseNode.class, "getMaxScore not defined when hasScore set to false", null); } ModuleConfiguration config = getModuleConfiguration(); Float max = (Float) config.get(MSCourseNode.CONFIG_KEY_SCORE_MAX); return max; } /** * @see org.olat.course.nodes.AssessableCourseNode#getMinScoreConfiguration() */ @Override public Float getMinScoreConfiguration() { if (!hasScoreConfigured()) { throw new OLATRuntimeException(ProjectBrokerCourseNode.class, "getMinScore not defined when hasScore set to false", null); } ModuleConfiguration config = getModuleConfiguration(); Float min = (Float) config.get(MSCourseNode.CONFIG_KEY_SCORE_MIN); return min; } /** * @see org.olat.course.nodes.AssessableCourseNode#getCutValueConfiguration() */ @Override public Float getCutValueConfiguration() { if (!hasPassedConfigured()) { throw new OLATRuntimeException(ProjectBrokerCourseNode.class, "getCutValue not defined when hasPassed set to false", null); } ModuleConfiguration config = getModuleConfiguration(); Float cut = (Float) config.get(MSCourseNode.CONFIG_KEY_PASSED_CUT_VALUE); return cut; } /** * @see org.olat.course.nodes.AssessableCourseNode#getUserCoachComment(org.olat.course.run.userview.UserCourseEnvironment) */ @Override public String getUserCoachComment(UserCourseEnvironment userCourseEnvironment) { AssessmentManager am = userCourseEnvironment.getCourseEnvironment().getAssessmentManager(); String coachCommentValue = am.getNodeCoachComment(this, userCourseEnvironment.getIdentityEnvironment().getIdentity()); return coachCommentValue; } /** * @see org.olat.course.nodes.AssessableCourseNode#getUserUserComment(org.olat.course.run.userview.UserCourseEnvironment) */ @Override public String getUserUserComment(UserCourseEnvironment userCourseEnvironment) { AssessmentManager am = userCourseEnvironment.getCourseEnvironment().getAssessmentManager(); String userCommentValue = am.getNodeComment(this, userCourseEnvironment.getIdentityEnvironment().getIdentity()); return userCommentValue; } /** * @see org.olat.course.nodes.AssessableCourseNode#getUserLog(org.olat.course.run.userview.UserCourseEnvironment) */ @Override public String getUserLog(UserCourseEnvironment userCourseEnvironment) { UserNodeAuditManager am = userCourseEnvironment.getCourseEnvironment().getAuditManager(); String logValue = am.getUserNodeLog(this, userCourseEnvironment.getIdentityEnvironment().getIdentity()); return logValue; } /** * @see org.olat.course.nodes.AssessableCourseNode#isEditableConfigured() */ @Override public boolean isEditableConfigured() { // always true when assessable return false; } /** * @see org.olat.course.nodes.AssessableCourseNode#updateUserCoachComment(java.lang.String, * org.olat.course.run.userview.UserCourseEnvironment) */ @Override public void updateUserCoachComment(String coachComment, UserCourseEnvironment userCourseEnvironment) { AssessmentManager am = userCourseEnvironment.getCourseEnvironment().getAssessmentManager(); Identity mySelf = userCourseEnvironment.getIdentityEnvironment().getIdentity(); if (coachComment != null) { am.saveNodeCoachComment(this, mySelf, coachComment); } } /** * @see org.olat.course.nodes.AssessableCourseNode#updateUserScoreEvaluation(org.olat.course.run.scoring.ScoreEvaluation, * org.olat.course.run.userview.UserCourseEnvironment, * org.olat.core.id.Identity) */ @Override public void updateUserScoreEvaluation(ScoreEvaluation scoreEvaluation, UserCourseEnvironment userCourseEnvironment, Identity coachingIdentity, boolean incrementAttempts) { AssessmentManager am = userCourseEnvironment.getCourseEnvironment().getAssessmentManager(); Identity mySelf = userCourseEnvironment.getIdentityEnvironment().getIdentity(); am.saveScoreEvaluation(this, coachingIdentity, mySelf, new ScoreEvaluation(scoreEvaluation), userCourseEnvironment, incrementAttempts); } /** * @see org.olat.course.nodes.AssessableCourseNode#updateUserUserComment(java.lang.String, * org.olat.course.run.userview.UserCourseEnvironment, * org.olat.core.id.Identity) */ @Override public void updateUserUserComment(String userComment, UserCourseEnvironment userCourseEnvironment, Identity coachingIdentity) { AssessmentManager am = userCourseEnvironment.getCourseEnvironment().getAssessmentManager(); Identity mySelf = userCourseEnvironment.getIdentityEnvironment().getIdentity(); if (userComment != null) { am.saveNodeComment(this, coachingIdentity, mySelf, userComment); } } /** * @see org.olat.course.nodes.AssessableCourseNode#getUserAttempts(org.olat.course.run.userview.UserCourseEnvironment) */ @Override public Integer getUserAttempts(UserCourseEnvironment userCourseEnvironment) { AssessmentManager am = userCourseEnvironment.getCourseEnvironment().getAssessmentManager(); Identity mySelf = userCourseEnvironment.getIdentityEnvironment().getIdentity(); Integer userAttemptsValue = am.getNodeAttempts(this, mySelf); return userAttemptsValue; } /** * @see org.olat.course.nodes.AssessableCourseNode#hasAttemptsConfigured() */ @Override public boolean hasAttemptsConfigured() { return false; } /** * @see org.olat.course.nodes.AssessableCourseNode#updateUserAttempts(java.lang.Integer, * org.olat.course.run.userview.UserCourseEnvironment, * org.olat.core.id.Identity) */ @Override public void updateUserAttempts(Integer userAttempts, UserCourseEnvironment userCourseEnvironment, Identity coachingIdentity) { if (userAttempts != null) { AssessmentManager am = userCourseEnvironment.getCourseEnvironment().getAssessmentManager(); Identity mySelf = userCourseEnvironment.getIdentityEnvironment().getIdentity(); am.saveNodeAttempts(this, coachingIdentity, mySelf, userAttempts); } } /** * @see org.olat.course.nodes.AssessableCourseNode#incrementUserAttempts(org.olat.course.run.userview.UserCourseEnvironment) */ @Override public void incrementUserAttempts(UserCourseEnvironment userCourseEnvironment) { AssessmentManager am = userCourseEnvironment.getCourseEnvironment().getAssessmentManager(); Identity mySelf = userCourseEnvironment.getIdentityEnvironment().getIdentity(); am.incrementNodeAttempts(this, mySelf, userCourseEnvironment); } /** * @see org.olat.course.nodes.AssessableCourseNode#getDetailsEditController(org.olat.core.gui.UserRequest, * org.olat.core.gui.control.WindowControl, * org.olat.course.run.userview.UserCourseEnvironment) */ @Override public Controller getDetailsEditController(UserRequest ureq, WindowControl wControl, BreadcrumbPanel stackPanel, UserCourseEnvironment coachCourseEnv, UserCourseEnvironment assessedUserCourseEnv) { // prepare file component throw new AssertException("ProjectBroker does not support AssessmentTool"); } /** Factory method to launch course element assessment tools. limitToGroup is optional to skip he the group choose step */ @Override public List<Controller> createAssessmentTools(UserRequest ureq, WindowControl wControl, TooledStackedPanel stackPanel, UserCourseEnvironment coachCourseEnv, AssessmentToolOptions options) { List<Controller> tools = new ArrayList<>(1); if(!coachCourseEnv.isCourseReadOnly()) { CourseEnvironment courseEnv = coachCourseEnv.getCourseEnvironment(); tools.add(new BulkAssessmentToolController(ureq, wControl, courseEnv, this)); } return tools; } /** * @see org.olat.course.nodes.AssessableCourseNode#getDetailsListView(org.olat.course.run.userview.UserCourseEnvironment) */ @Override public String getDetailsListView(UserCourseEnvironment userCourseEnvironment) { Identity identity = userCourseEnvironment.getIdentityEnvironment().getIdentity(); CoursePropertyManager propMgr = userCourseEnvironment.getCourseEnvironment().getCoursePropertyManager(); List<Property> samples = propMgr.findCourseNodeProperties(this, identity, null, TaskController.PROP_ASSIGNED); if (samples.size() == 0) return null; // no sample assigned yet return samples.get(0).getStringValue(); } @Override public String getDetailsListViewHeaderKey() { return "table.header.details.ta"; } @Override public boolean hasDetails() { Boolean hasDropbox = (Boolean) getModuleConfiguration().get(CONF_DROPBOX_ENABLED); if (hasDropbox == null) hasDropbox = Boolean.FALSE; return hasDropbox.booleanValue(); } @Override public void postImport(File importDirectory, ICourse course, CourseEnvironmentMapper envMapper, Processing processType) { // initialize managers if(processType == Processing.editor && importDirectory != null) { ProjectBrokerManager projectBrokerManager = CoreSpringFactory.getImpl(ProjectBrokerManager.class); CoursePropertyManager cpm = course.getCourseEnvironment().getCoursePropertyManager(); // create a new projectBroker ProjectBroker projectBroker = projectBrokerManager.createAndSaveProjectBroker(); projectBrokerManager.saveProjectBrokerId(projectBroker.getKey(), cpm, this); // get the node folder inside of the importDirectory File folderNodeData = new File(importDirectory, getIdent()); //for the broker prefs File projectBrokerFile = new File(folderNodeData, "projectbroker.xml"); if(projectBrokerFile.exists()) { XStream xstream = XStreamHelper.createXStreamInstance(); ProjectGroupManager projectGroupManager = CoreSpringFactory.getImpl(ProjectGroupManager.class); ProjectBrokerConfig brokerConfig = (ProjectBrokerConfig)XStreamHelper.readObject(xstream, projectBrokerFile); if(brokerConfig != null && brokerConfig.getAccountGroupKey() != null) { Long accountGroupKey = envMapper.toGroupKeyFromOriginalKey(brokerConfig.getAccountGroupKey()); if(accountGroupKey != null) { projectGroupManager.saveAccountManagerGroupKey(accountGroupKey, cpm, this); } } } // loop through the project directories if (folderNodeData.exists()) { for (File projectDir : folderNodeData.listFiles(DirectoryFilter.DIRECTORY_FILTER)) { for (File projectFile : projectDir.listFiles(new FileNameSuffixFilter("xml"))) { importProject(projectDir, projectFile, projectBroker, course, envMapper); } } } } super.postImport(importDirectory, course, envMapper, processType); } private void importProject(File projectDir, File projectFile, ProjectBroker projectBroker, ICourse course, CourseEnvironmentMapper envMapper) { XStream xstream = XStreamHelper.createXStreamInstance(); BusinessGroupService bgs = CoreSpringFactory.getImpl(BusinessGroupService.class); CoursePropertyManager cpm = course.getCourseEnvironment().getCoursePropertyManager(); ProjectGroupManager projectGroupManager = CoreSpringFactory.getImpl(ProjectGroupManager.class); ProjectBrokerManager projectBrokerManager = CoreSpringFactory.getImpl(ProjectBrokerManager.class); // read the projectConfiguration from the importDirectory try { @SuppressWarnings("unchecked") Map<String, Object> projectConfig = (HashMap<String, Object>) XStreamHelper.readObject(xstream, projectFile); String projectTitle = (String)projectConfig.get("title"); Long originalGroupKey = null; if(projectConfig.containsKey("businessGroupKey")) { originalGroupKey = (Long)projectConfig.get("businessGroupKey"); } else { for(BusinessGroupReference ref:envMapper.getGroups()) { if(ref.getName().endsWith(projectTitle)) { originalGroupKey = ref.getOriginalKey(); } } } BusinessGroup projectGroup = null; if(originalGroupKey != null) { Long groupKey = envMapper.toGroupKeyFromOriginalKey(originalGroupKey); projectGroup = bgs.loadBusinessGroup(groupKey); } if(projectGroup == null) { projectGroup = projectGroupManager.createProjectGroupFor(projectBroker.getKey(), envMapper.getAuthor(), projectTitle, (String)projectConfig.get("description"), course.getResourceableId()); } if(envMapper.getAuthor() != null) { Identity author = envMapper.getAuthor(); bgs.addOwners(author, null, Collections.singletonList(author), projectGroup, null); } Project project = projectBrokerManager.createAndSaveProjectFor(projectTitle, (String)projectConfig.get("description"), projectBrokerManager.getProjectBrokerId(cpm, this), projectGroup); projectGroupManager.setDeselectionAllowed(project, (boolean) projectConfig.get("allowDeselection")); project.setMailNotificationEnabled((boolean) projectConfig.get("mailNotificationEnabled")); project.setMaxMembers((int) projectConfig.get("maxMembers")); project.setAttachedFileName(projectConfig.get("attachmentFileName").toString()); for (int i = 0; i < (int) projectConfig.get("customeFieldSize"); i++) { project.setCustomFieldValue(i, projectConfig.get("customFieldValue" + i).toString()); } projectBrokerManager.updateProject(project); // get the attachment directory within the project // directory File attachmentDir = new File(projectDir, "attachment");// .getParentFile().listFiles(attachmentFilter); if (attachmentDir.exists()) { File[] attachment = attachmentDir.listFiles(); if (attachment.length > 0) { VFSLeaf attachmentLeaf = new LocalFileImpl(attachment[0]); projectBrokerManager.saveAttachedFile(project, projectConfig.get("attachmentFileName").toString(), attachmentLeaf, course.getCourseEnvironment(), this); } } } catch (Exception e) { // handle/log error in case of FileIO exception or cast // exception if import input is not correct log.error("Error while importing a project into projectbroker", e); } } @Override public void importNode(File importDirectory, ICourse course, Identity owner, Locale locale, boolean withReferences) { super.importNode(importDirectory, course, owner, locale, withReferences); } @Override public void exportNode(File exportDirectory, ICourse course) { // initialize managers CoursePropertyManager cpm = course.getCourseEnvironment().getCoursePropertyManager(); ProjectBrokerManager projectBrokerManager = CoreSpringFactory.getImpl(ProjectBrokerManager.class); ProjectBroker pb = projectBrokerManager.getProjectBroker(projectBrokerManager.getProjectBrokerId(cpm, this)); ProjectGroupManager projectGroupManager = CoreSpringFactory.getImpl(ProjectGroupManager.class); XStream xstream = XStreamHelper.createXStreamInstance(); // folder for the pb node File pbNodeFolder = new File(exportDirectory, getIdent()); pbNodeFolder.mkdirs(); //for the broker prefs ProjectBrokerConfig brokerConfig = new ProjectBrokerConfig(); brokerConfig.setAccountGroupKey(projectGroupManager.getAccountManagerGroupKey(cpm, this)); File projectBrokerFile = new File(pbNodeFolder, "projectbroker.xml"); XStreamHelper.writeObject(xstream, projectBrokerFile, brokerConfig); // get all the projects available in the pb List<Project> projects = projectBrokerManager.getProjectListBy(pb.getKey()); for (Project project:projects) { File projectFolder = new File(pbNodeFolder, project.getKey().toString()); projectFolder.mkdirs(); // create a hashmap with the project configuration and insert the // project data File projectFile = new File(projectFolder, project.getKey() + ".xml"); HashMap<String, Object> projectData = new HashMap<String, Object>(); projectData.put("title", project.getTitle()); projectData.put("description", project.getDescription()); projectData.put("customFieldSize", project.getCustomFieldSize()); projectData.put("maxMembers", project.getMaxMembers()); projectData.put("mailNotificationEnabled", project.isMailNotificationEnabled()); projectData.put("attachmentFileName", project.getAttachmentFileName()); projectData.put("allowDeselection", projectGroupManager.isDeselectionAllowed(project)); projectData.put("customeFieldSize", project.getCustomFieldSize()); projectData.put("businessGroupKey", project.getProjectGroup().getKey()); // iterate through the customFields for (int i = 0; i < project.getCustomFieldSize(); i++) { projectData.put("customFieldValue" + i, project.getCustomFieldValue(i)); } // writeout the project data XStreamHelper.writeObject(xstream, projectFile, projectData); // add attachment file OlatRootFolderImpl rootFolder = new OlatRootFolderImpl(projectBrokerManager.getAttamchmentRelativeRootPath(project, course.getCourseEnvironment(), this), null); VFSItem item = rootFolder.resolve(project.getAttachmentFileName()); if (item instanceof VFSLeaf) { VFSLeaf itemLeaf = (VFSLeaf) item; File attachmentFolder = new File(projectFolder, "attachment"); File attachment = new File(attachmentFolder, Base64.encodeBase64String(project.getAttachmentFileName().getBytes())); try { attachmentFolder.mkdirs(); attachment.createNewFile(); FileOutputStream attachmentOutputStream = new FileOutputStream(attachment); InputStream leafInputStream = itemLeaf.getInputStream(); FileUtils.copy(leafInputStream, attachmentOutputStream); attachmentOutputStream.close(); leafInputStream.close(); } catch (IOException e) { log.error("Error while exporting attachments for projectbroker " + project.getTitle(), e); } } } } @Override public boolean archiveNodeData(Locale locale, ICourse course, ArchiveOptions options, ZipOutputStream exportStream, String charset) { boolean dataFound = false; String dropboxPath = DropboxController.getDropboxPathRelToFolderRoot(course.getCourseEnvironment(),this); OlatRootFolderImpl dropboxDir = new OlatRootFolderImpl(dropboxPath, null); String returnboxPath = ReturnboxController.getReturnboxPathRelToFolderRoot(course.getCourseEnvironment(),this); OlatRootFolderImpl returnboxDir = new OlatRootFolderImpl(returnboxPath, null); if (!dropboxDir.exists() && !returnboxDir.exists()) { return false; } String exportDirName = "projectbroker_" + Formatter.makeStringFilesystemSave(getShortName()) + "_" + Formatter.formatDatetimeFilesystemSave(new Date(System.currentTimeMillis())); try { String projectBrokerTableExport = ProjectBrokerExportGenerator.createCourseResultsOverviewTable(this, course, locale); String tableExportFileName = ExportUtil.createFileNameWithTimeStamp(getShortTitle() + "-projectbroker_overview", "xls"); exportStream.putNextEntry(new ZipEntry(exportDirName + "/" + tableExportFileName)); IOUtils.write(projectBrokerTableExport, exportStream); exportStream.closeEntry(); } catch (IOException e) { log.error("", e); } // copy dropboxes to tmp dir if (dropboxDir.exists()) { //OLAT-6426 archive only dropboxes of users that handed in at least one file -> prevent empty folders in archive for(VFSItem themaItem: dropboxDir.getItems()) { if (!(themaItem instanceof VFSContainer)) continue; List<VFSItem> userFolderArray = ((VFSContainer)themaItem).getItems(); for (VFSItem userFolder : userFolderArray){ if (!VFSManager.isDirectoryAndNotEmpty(userFolder)) continue; String path = exportDirName + "/dropboxes/" + themaItem.getName(); ZipUtil.addToZip(userFolder, path, exportStream); } } } // copy returnboxes to tmp dir if (returnboxDir.exists()) { for (VFSItem themaItem:returnboxDir.getItems()) { if (!(themaItem instanceof VFSContainer)) continue; List<VFSItem> userFolderArray = ((VFSContainer)themaItem).getItems(); for (VFSItem userFolder : userFolderArray){ if (!VFSManager.isDirectoryAndNotEmpty(userFolder)) continue; String path = exportDirName + "/returnboxes/" + themaItem.getName(); ZipUtil.addToZip(userFolder, path, exportStream); } } } return dataFound; } /** * @see org.olat.course.nodes.GenericCourseNode#getConditionExpressions() */ @Override public List<ConditionExpression> getConditionExpressions() { List<ConditionExpression> retVal; List<ConditionExpression> parentsConditions = super.getConditionExpressions(); if (parentsConditions.size() > 0) { retVal = new ArrayList<ConditionExpression>(parentsConditions); } else { retVal = new ArrayList<ConditionExpression>(); } // String conditionProjectBroker = getConditionProjectBroker().getConditionExpression(); if (conditionProjectBroker != null && !conditionProjectBroker.equals("")) { // an active condition is defined ConditionExpression ce = new ConditionExpression(getConditionProjectBroker().getConditionId()); ce.setExpressionString(getConditionProjectBroker().getConditionExpression()); retVal.add(ce); } // return retVal; } /** * Init config parameter with default values for a new course node. */ @Override public void updateModuleConfigDefaults(boolean isNewNode) { ModuleConfiguration config = getModuleConfiguration(); if (isNewNode) { // use defaults for new course building blocks // dropbox defaults config.set(CONF_DROPBOX_ENABLED, Boolean.TRUE); config.set(CONF_DROPBOX_ENABLEMAIL, Boolean.FALSE); config.set(CONF_DROPBOX_CONFIRMATION, ""); // scoring defaults config.set(CONF_SCORING_ENABLED, Boolean.FALSE); // returnbox defaults config.set(CONF_RETURNBOX_ENABLED, Boolean.TRUE); // New config parameter version 2 config.setBooleanEntry(CONF_TASK_PREVIEW, false); MSCourseNode.initDefaultConfig(config); config.setConfigurationVersion(CURRENT_CONFIG_VERSION); } else { int version = config.getConfigurationVersion(); if (version < CURRENT_CONFIG_VERSION) { // Loaded config is older than current config version => migrate if (version == 1) { // migrate V1 => V2 (remove all condition this.setConditionDrop(null); this.setConditionReturnbox(null); version = 2; } config.setConfigurationVersion(CURRENT_CONFIG_VERSION); } } } @Override protected void postImportCopyConditions(CourseEnvironmentMapper envMapper) { super.postImportCopyConditions(envMapper); postImportCondition(conditionDrop, envMapper); postImportCondition(conditionScoring, envMapper); postImportCondition(conditionReturnbox, envMapper); postImportCondition(conditionProjectBroker, envMapper); } @Override public void postExport(CourseEnvironmentMapper envMapper, boolean backwardsCompatible) { super.postExport(envMapper, backwardsCompatible); postExportCondition(conditionDrop, envMapper, backwardsCompatible); postExportCondition(conditionScoring, envMapper, backwardsCompatible); postExportCondition(conditionReturnbox, envMapper, backwardsCompatible); postExportCondition(conditionProjectBroker, envMapper, backwardsCompatible); } /** * Do re-arrange the projects in a new project broker after the copy happened */ @Override public void postCopy(CourseEnvironmentMapper envMapper, Processing processType, ICourse course, ICourse sourceCourse) { super.postCopy(envMapper, processType, course, null); if(processType.equals(Processing.runstructure)) { //initialize the managers and services ProjectBrokerManager projectBrokerManager = CoreSpringFactory.getImpl(ProjectBrokerManager.class); ProjectGroupManager projectGroupManager = CoreSpringFactory.getImpl(ProjectGroupManager.class); CoursePropertyManager oldCpm = sourceCourse.getCourseEnvironment().getCoursePropertyManager(); BusinessGroupService bgs = CoreSpringFactory.getImpl(BusinessGroupService.class); //create new Project broker and get the old one Long projectBrokerId = projectBrokerManager.createAndSaveProjectBroker().getKey(); projectBrokerManager.saveProjectBrokerId(projectBrokerId, course.getCourseEnvironment().getCoursePropertyManager(), this); //find the group for account manager and remap the account group CourseNode sourceCourseNode = sourceCourse.getRunStructure().getNode(getIdent()); Long sourceAccountGroupKey = projectGroupManager.getAccountManagerGroupKey(oldCpm, sourceCourseNode); if(sourceAccountGroupKey != null) { Long copiedGroupKey = envMapper.toGroupKeyFromOriginalKey(sourceAccountGroupKey); CoursePropertyManager cpm = course.getCourseEnvironment().getCoursePropertyManager(); projectGroupManager.saveAccountManagerGroupKey(copiedGroupKey, cpm, this); } Long oldBrokerId = projectBrokerManager.getProjectBrokerId(oldCpm, this); List<Project> projectsFromGroup = projectBrokerManager.getProjectListBy(oldBrokerId); //loop create and configure the new Projects for(Project project : projectsFromGroup){ Long originalGroupKey = project.getProjectGroup().getKey(); Long copiedGroupKey = envMapper.toGroupKeyFromOriginalKey(originalGroupKey); Identity author = envMapper.getAuthor(); BusinessGroup projectGroup = bgs.loadBusinessGroup(copiedGroupKey); if (projectGroup == null) { projectGroup = projectGroupManager.createProjectGroupFor(projectBrokerId, author, project.getTitle(), project.getDescription(), course.getResourceableId()); } if(author != null) { bgs.addOwners(author, null, Collections.singletonList(author), projectGroup, null); } Project newProject = projectBrokerManager.createAndSaveProjectFor(project.getTitle(), project.getDescription(), projectBrokerId, projectGroup); // copy all project configurations newProject.setMailNotificationEnabled(project.isMailNotificationEnabled()); newProject.setMaxMembers(project.getMaxMembers()); for (int i = 0; i < project.getCustomFieldSize(); i++) { newProject.setCustomFieldValue(i, project.getCustomFieldValue(i)); } projectGroupManager.setDeselectionAllowed(newProject, project.getProjectGroup().isAllowToLeave()); projectBrokerManager.updateProject(newProject); // attachment file OlatRootFolderImpl rootFolder = new OlatRootFolderImpl(projectBrokerManager.getAttamchmentRelativeRootPath(project, sourceCourse.getCourseEnvironment(), this), null); VFSItem item = rootFolder.resolve(project.getAttachmentFileName()); if (item instanceof VFSLeaf) { projectBrokerManager.saveAttachedFile(newProject, project.getAttachmentFileName(), (VFSLeaf) item, course.getCourseEnvironment(), this); newProject.setAttachedFileName(project.getAttachmentFileName()); projectBrokerManager.updateProject(newProject); } } } } /** * @see org.olat.course.nodes.CourseNode#createInstanceForCopy() */ @Override public CourseNode createInstanceForCopy(boolean isNewTitle, ICourse course, Identity author) { // create the instance for the copy CourseNode copyInstance = super.createInstanceForCopy(isNewTitle, course, author); // get all the different managers BusinessGroupService bgs = CoreSpringFactory.getImpl(BusinessGroupService.class); CoursePropertyManager cpm = course.getCourseEnvironment().getCoursePropertyManager(); ProjectGroupManager projectGroupManager = CoreSpringFactory.getImpl(ProjectGroupManager.class); ProjectBrokerManager projectBrokerManager = CoreSpringFactory.getImpl(ProjectBrokerManager.class); // get the pbID from the source pb Long oldProjectBrokerId = projectBrokerManager.getProjectBrokerId(cpm, this); // create a new projectBroker for the copyInstance ProjectBroker newBroker = projectBrokerManager.createAndSaveProjectBroker(); Long projectBrokerId = newBroker.getKey(); projectBrokerManager.saveProjectBrokerId(projectBrokerId, cpm, copyInstance); // configure the new Project like the old one // copy the old accountManagergroup to preserve the // "persons in charge" Long originalAccountGroupKey = projectGroupManager.getAccountManagerGroupKey(cpm, this); if(originalAccountGroupKey != null) { BusinessGroup originalAccountGroup = projectGroupManager.getAccountManagerGroupFor(cpm, this, course, getShortTitle(), getShortTitle(), null); BusinessGroup newAccountManagerGroup = bgs.copyBusinessGroup(author, originalAccountGroup, originalAccountGroup.getName(), originalAccountGroup.getDescription(), originalAccountGroup.getMinParticipants(), originalAccountGroup.getMaxParticipants(), false, false, true, false, false, true, false, false); projectGroupManager.saveAccountManagerGroupKey(newAccountManagerGroup.getKey(), cpm, copyInstance); bgs.addResourceTo(newAccountManagerGroup, course.getCourseEnvironment().getCourseGroupManager().getCourseEntry()); } if (oldProjectBrokerId != null) { List<Project> projects = projectBrokerManager.getProjectListBy(oldProjectBrokerId); for (Project project: projects) { // create projectGroup BusinessGroup projectGroup = projectGroupManager.createProjectGroupFor(projectBrokerId, author, project.getTitle(), project.getDescription(), course.getResourceableId()); Project newProject = projectBrokerManager.createAndSaveProjectFor(project.getTitle(), project.getDescription(), projectBrokerId, projectGroup); // copy all project configurations newProject.setMailNotificationEnabled(project.isMailNotificationEnabled()); newProject.setMaxMembers(project.getMaxMembers()); for (int i = 0; i < project.getCustomFieldSize(); i++) { newProject.setCustomFieldValue(i, project.getCustomFieldValue(i)); } projectGroupManager.setDeselectionAllowed(newProject, project.getProjectGroup().isAllowToLeave()); projectBrokerManager.updateProject(newProject); // attachment file OlatRootFolderImpl rootFolder = new OlatRootFolderImpl(projectBrokerManager.getAttamchmentRelativeRootPath(project, course.getCourseEnvironment(), this), null); VFSItem item = rootFolder.resolve(project.getAttachmentFileName()); if (item instanceof VFSLeaf) { projectBrokerManager.saveAttachedFile(newProject, project.getAttachmentFileName(), (VFSLeaf) item, course.getCourseEnvironment(), copyInstance); newProject.setAttachedFileName(project.getAttachmentFileName()); projectBrokerManager.updateProject(newProject); } } } return copyInstance; } public static class ProjectBrokerConfig implements Serializable { private static final long serialVersionUID = -1002067261836601966L; private Long accountGroupKey; public Long getAccountGroupKey() { return accountGroupKey; } public void setAccountGroupKey(Long accountGroupKey) { this.accountGroupKey = accountGroupKey; } } }