From 82dd09f3d789ddf3c36ab2161ab5d4a62ff54780 Mon Sep 17 00:00:00 2001
From: srosse <stephane.rosse@frentix.com>
Date: Fri, 5 Jul 2019 07:41:53 +0200
Subject: [PATCH] OO-3887: reimplement options for Adobe Connect

---
 .../collaboration/CollaborationTools.java     |   2 +-
 .../olat/commons/calendar/CalendarUtils.java  |  16 +-
 .../scheduler/_spring/schedulerContext.xml    |   1 +
 .../course/nodes/AdobeConnectCourseNode.java  |  27 +-
 .../adobeconnect/AdobeConnectConfigForm.java  |  85 ++++++
 .../AdobeConnectEditController.java           |  26 +-
 .../_i18n/LocalStrings_de.properties          |   4 +
 .../_i18n/LocalStrings_en.properties          |   8 +-
 .../_i18n/LocalStrings_fr.properties          |   4 +
 .../_i18n/LocalStrings_pt_BR.properties       |   6 +
 .../olat/modules/_spring/modulesContext.xml   |  21 +-
 .../adobeconnect/AdobeConnectManager.java     |  37 ++-
 .../adobeconnect/AdobeConnectMeeting.java     |  24 ++
 .../adobeconnect/AdobeConnectModule.java      |  73 ++++-
 .../_spring/adobeConnectContext.xml           |  40 +++
 .../manager/AbstractAdobeConnectProvider.java |  21 +-
 .../manager/AdobeConnect9Provider.java        |   5 +-
 .../manager/AdobeConnectCleanupJob.java       |  77 +++++
 .../manager/AdobeConnectManagerImpl.java      | 267 ++++++++++++++++--
 .../manager/AdobeConnectMeetingDAO.java       |  93 +++++-
 .../adobeconnect/manager/AdobeConnectSPI.java |   2 +
 .../manager/AdobeConnectUtils.java            |  12 +-
 .../adobeconnect/manager/DFNprovider.java     |   4 +-
 .../manager/NoAdapterProvider.java            |   6 +
 .../model/AdobeConnectErrors.java             |   9 +
 .../model/AdobeConnectMeetingImpl.java        |  93 +++++-
 .../adobeconnect/model/AdobeConnectSco.java   |   9 +
 .../AdobeConnectAdminMeetingsController.java  |   6 +
 .../AdobeConnectConfigurationController.java  |  56 ++++
 .../AdobeConnectEditMeetingsController.java   |   6 +
 .../adobeconnect/ui/AdobeConnectEvent.java    |  53 ++++
 .../ui/AdobeConnectMeetingController.java     | 174 ++++++++++--
 ...obeConnectMeetingDefaultConfiguration.java |  14 +-
 .../ui/AdobeConnectMeetingTableModel.java     |   2 +
 .../ui/AdobeConnectMeetingsController.java    |  13 +-
 .../ui/AdobeConnectRunController.java         |  42 ++-
 .../AdobeConnectShareDocumentsController.java |  19 ++
 .../ui/EditAdobeConnectMeetingController.java |  71 ++++-
 .../adobeconnect/ui/_content/meeting.html     |  62 ++--
 .../ui/_i18n/LocalStrings_de.properties       |  19 ++
 .../ui/_i18n/LocalStrings_en.properties       |  23 +-
 .../ui/_i18n/LocalStrings_fr.properties       |   7 +-
 .../_spring/databaseUpgradeContext.xml        |   4 +
 .../database/mysql/alter_14_0_0_to_14_0_1.sql |   8 +
 .../database/mysql/setupDatabase.sql          |   7 +
 .../oracle/alter_14_0_0_to_14_0_1.sql         |   8 +
 .../database/oracle/setupDatabase.sql         |   7 +
 .../postgresql/alter_14_0_0_to_14_0_1.sql     |   8 +
 .../database/postgresql/setupDatabase.sql     |   7 +
 .../manager/AdobeConnectMeetingDAOTest.java   |  51 +++-
 50 files changed, 1473 insertions(+), 166 deletions(-)
 create mode 100644 src/main/java/org/olat/course/nodes/adobeconnect/AdobeConnectConfigForm.java
 create mode 100644 src/main/java/org/olat/course/nodes/adobeconnect/_i18n/LocalStrings_pt_BR.properties
 create mode 100644 src/main/java/org/olat/modules/adobeconnect/_spring/adobeConnectContext.xml
 create mode 100644 src/main/java/org/olat/modules/adobeconnect/manager/AdobeConnectCleanupJob.java
 create mode 100644 src/main/java/org/olat/modules/adobeconnect/ui/AdobeConnectEvent.java
 create mode 100644 src/main/resources/database/mysql/alter_14_0_0_to_14_0_1.sql
 create mode 100644 src/main/resources/database/oracle/alter_14_0_0_to_14_0_1.sql
 create mode 100644 src/main/resources/database/postgresql/alter_14_0_0_to_14_0_1.sql

diff --git a/src/main/java/org/olat/collaboration/CollaborationTools.java b/src/main/java/org/olat/collaboration/CollaborationTools.java
index eb8409067eb..0589bfbf515 100644
--- a/src/main/java/org/olat/collaboration/CollaborationTools.java
+++ b/src/main/java/org/olat/collaboration/CollaborationTools.java
@@ -619,7 +619,7 @@ public class CollaborationTools implements Serializable {
 	}
 	
 	public Controller createAdobeConnectController(final UserRequest ureq, WindowControl wControl, final BusinessGroup group, boolean admin) {
-		AdobeConnectMeetingDefaultConfiguration configuration = new AdobeConnectMeetingDefaultConfiguration(true);
+		AdobeConnectMeetingDefaultConfiguration configuration = new AdobeConnectMeetingDefaultConfiguration(true, true, true);
 		return new AdobeConnectRunController(ureq, wControl, null, null, group, configuration, admin, admin, false);
 	}
 
diff --git a/src/main/java/org/olat/commons/calendar/CalendarUtils.java b/src/main/java/org/olat/commons/calendar/CalendarUtils.java
index 6b183a75ba9..548ad882e10 100644
--- a/src/main/java/org/olat/commons/calendar/CalendarUtils.java
+++ b/src/main/java/org/olat/commons/calendar/CalendarUtils.java
@@ -87,7 +87,13 @@ public class CalendarUtils {
 		return cal;
 	}
 	
-	
+	public static Calendar getStartOfDay(Calendar cal)  {
+		cal.set(Calendar.HOUR_OF_DAY, 0);
+		cal.set(Calendar.MINUTE, 0);
+		cal.set(Calendar.SECOND, 0);
+		cal.set(Calendar.MILLISECOND, 0);
+		return cal;
+	}
 	
 	public static Date endOfDay(Date date) {
 		Calendar cal = Calendar.getInstance();
@@ -124,7 +130,7 @@ public class CalendarUtils {
 				String frequency = recur.getFrequency();
 				WeekDayList wdl = recur.getDayList();
 				Integer interval = recur.getInterval();
-				if((wdl != null && wdl.size() > 0)) {
+				if((wdl != null && !wdl.isEmpty())) {
 					// we only support one rule with daylist
 					return KalendarEvent.WORKDAILY;
 				} else if(interval != null && interval == 2) {
@@ -142,10 +148,6 @@ public class CalendarUtils {
 		return null;
 	}
 	
-
-	
-
-	
 	/**
 	 * Create list with excluded dates based on the exclusion rule.
 	 * @param recurrenceExc
@@ -176,7 +178,7 @@ public class CalendarUtils {
 	 * @return string with exclude rule
 	 */
 	public static String getRecurrenceExcludeRule(List<Date> dates) {
-		if(dates != null && dates.size() > 0) {
+		if(dates != null && !dates.isEmpty()) {
 			DateList dl = new DateList();
 			for( Date date : dates ) {
 				net.fortuna.ical4j.model.Date dd = CalendarUtils.createDate(date);
diff --git a/src/main/java/org/olat/core/commons/services/scheduler/_spring/schedulerContext.xml b/src/main/java/org/olat/core/commons/services/scheduler/_spring/schedulerContext.xml
index f27e65ef671..47a3ddc626e 100644
--- a/src/main/java/org/olat/core/commons/services/scheduler/_spring/schedulerContext.xml
+++ b/src/main/java/org/olat/core/commons/services/scheduler/_spring/schedulerContext.xml
@@ -55,6 +55,7 @@ How to add a new job:
             <ref bean="qualityTrigger"/>
             <ref bean="deleteUserDataExportTrigger"/>
             <ref bean="cspCleanupJob"/>
+            <ref bean="adobeCleanupTrigger"/>
         </list>
     </property>
 </bean>
diff --git a/src/main/java/org/olat/course/nodes/AdobeConnectCourseNode.java b/src/main/java/org/olat/course/nodes/AdobeConnectCourseNode.java
index 5d47185ac00..f2ca37bc81f 100644
--- a/src/main/java/org/olat/course/nodes/AdobeConnectCourseNode.java
+++ b/src/main/java/org/olat/course/nodes/AdobeConnectCourseNode.java
@@ -41,6 +41,7 @@ import org.olat.course.groupsandrights.CourseGroupManager;
 import org.olat.course.nodes.adobeconnect.AdobeConnectCourseNodeConfiguration;
 import org.olat.course.nodes.adobeconnect.AdobeConnectEditController;
 import org.olat.course.nodes.adobeconnect.compatibility.AdobeConnectCompatibilityConfiguration;
+import org.olat.course.run.environment.CourseEnvironment;
 import org.olat.course.run.navigation.NodeRunConstructionResult;
 import org.olat.course.run.userview.NodeEvaluation;
 import org.olat.course.run.userview.UserCourseEnvironment;
@@ -71,15 +72,26 @@ public class AdobeConnectCourseNode extends AbstractAccessableCourseNode {
 		super(TYPE);
 	}
 
-	@Override
-	public void updateModuleConfigDefaults(boolean isNewNode) {
+	private void updateModuleConfigDefaults(CourseEnvironment courseEnv, boolean isNewNode) {
 		ModuleConfiguration config = getModuleConfiguration();
 		if(config.getConfigurationVersion() < 2) {
 			Object oldConfiguration = config.get(CONF_VC_CONFIGURATION);
 			if(oldConfiguration instanceof AdobeConnectCompatibilityConfiguration) {
 				AdobeConnectCompatibilityConfiguration oldConfig = (AdobeConnectCompatibilityConfiguration)oldConfiguration;
+				config.setBooleanEntry(AdobeConnectEditController.ACCESS_BY_DATES, oldConfig.isUseMeetingDates());
 				config.setBooleanEntry(AdobeConnectEditController.GUEST_ACCESS_ALLOWED, oldConfig.isGuestAccessAllowed());
 				config.setBooleanEntry(AdobeConnectEditController.MODERATOR_START_MEETING, !oldConfig.isGuestStartMeetingAllowed());
+				
+				if(courseEnv != null && oldConfig.getMeetingDatas() != null && !oldConfig.getMeetingDatas().isEmpty()) {
+					AdobeConnectManager adobeConnectManager = CoreSpringFactory.getImpl(AdobeConnectManager.class);
+					RepositoryEntry courseEntry = courseEnv.getCourseGroupManager().getCourseEntry();
+					boolean hasMeetings = adobeConnectManager.hasMeetings(courseEntry, getIdent(), null);
+					if(!hasMeetings) {
+						synchronized(this) {// enough
+							adobeConnectManager.convert(oldConfig.getMeetingDatas(), courseEntry, getIdent());
+						}
+					}
+				}
 			}
 		}
 		config.setConfigurationVersion(2);
@@ -94,7 +106,7 @@ public class AdobeConnectCourseNode extends AbstractAccessableCourseNode {
 	@Override
 	public TabbableController createEditController(UserRequest ureq, WindowControl wControl, BreadcrumbPanel stackPanel,
 			ICourse course, UserCourseEnvironment userCourseEnv) {
-		updateModuleConfigDefaults(false);
+		updateModuleConfigDefaults(userCourseEnv.getCourseEnvironment(), false);
 		
 		CourseNode chosenNode = course.getEditorTreeModel().getCourseNode(userCourseEnv.getCourseEditorEnv().getCurrentCourseNodeId());
 		// create edit controller
@@ -109,7 +121,7 @@ public class AdobeConnectCourseNode extends AbstractAccessableCourseNode {
 	@Override
 	public NodeRunConstructionResult createNodeRunConstructionResult(UserRequest ureq, WindowControl wControl,
 			UserCourseEnvironment userCourseEnv, NodeEvaluation ne, String nodecmd) {
-		updateModuleConfigDefaults(false);
+		updateModuleConfigDefaults(userCourseEnv.getCourseEnvironment(), false);
 		
 		String providerId = getModuleConfiguration().getStringValue("vc_provider_id");
 		
@@ -125,7 +137,12 @@ public class AdobeConnectCourseNode extends AbstractAccessableCourseNode {
 			boolean moderator = admin || userCourseEnv.isCoach();
 			// create run controller
 			RepositoryEntry entry = userCourseEnv.getCourseEnvironment().getCourseGroupManager().getCourseEntry();
-			AdobeConnectMeetingDefaultConfiguration configuration = new AdobeConnectMeetingDefaultConfiguration(true);
+			
+			ModuleConfiguration config = getModuleConfiguration();
+			boolean onlyDates = config.getBooleanSafe(AdobeConnectEditController.ACCESS_BY_DATES, false);
+			boolean guestAccess = config.getBooleanSafe(AdobeConnectEditController.GUEST_ACCESS_ALLOWED, false);
+			boolean moderatorStart = config.getBooleanSafe(AdobeConnectEditController.MODERATOR_START_MEETING, false);
+			AdobeConnectMeetingDefaultConfiguration configuration = new AdobeConnectMeetingDefaultConfiguration(onlyDates, guestAccess, moderatorStart);
 			controller = new AdobeConnectRunController(ureq, wControl, entry, getIdent(), null, configuration,
 					admin, moderator, userCourseEnv.isCourseReadOnly());
 		}
diff --git a/src/main/java/org/olat/course/nodes/adobeconnect/AdobeConnectConfigForm.java b/src/main/java/org/olat/course/nodes/adobeconnect/AdobeConnectConfigForm.java
new file mode 100644
index 00000000000..106bd5d1eb6
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/adobeconnect/AdobeConnectConfigForm.java
@@ -0,0 +1,85 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.course.nodes.adobeconnect;
+
+import java.util.Collection;
+
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.FormItemContainer;
+import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement;
+import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
+import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
+import org.olat.core.gui.control.Controller;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.course.editor.NodeEditController;
+import org.olat.modules.ModuleConfiguration;
+
+/**
+ * Initial Date: 3  juil. 2019<br>
+ * 
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class AdobeConnectConfigForm extends FormBasicController {
+	
+	private static final String[] accessKeys = new String[] { "dates", "open", "start" };
+	
+	private MultipleSelectionElement accessEl;
+	
+	private final ModuleConfiguration config;
+
+	public AdobeConnectConfigForm(UserRequest ureq, WindowControl wControl, ModuleConfiguration config) {
+		super(ureq, wControl);
+		this.config = config;
+		
+		initForm(ureq);
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		String[] accessValues = new String[] { translate("vc.access.dates"), translate("vc.access.open"), translate("vc.access.start") };
+		boolean onlyDates = config.getBooleanSafe(AdobeConnectEditController.ACCESS_BY_DATES, false);
+		boolean guestAccess = config.getBooleanSafe(AdobeConnectEditController.GUEST_ACCESS_ALLOWED, false);
+		boolean moderatorStart = config.getBooleanSafe(AdobeConnectEditController.MODERATOR_START_MEETING, false);
+		accessEl = uifactory.addCheckboxesVertical("vc.access.label", "vc.access.label", formLayout, accessKeys, accessValues, 1);
+		accessEl.select(accessKeys[0], onlyDates);
+		accessEl.select(accessKeys[1], guestAccess);
+		accessEl.select(accessKeys[2], moderatorStart);
+
+		FormLayoutContainer buttonsCont = FormLayoutContainer.createButtonLayout("buttons", getTranslator());
+		formLayout.add(buttonsCont);
+		uifactory.addFormCancelButton("cancel", buttonsCont, ureq, getWindowControl());
+		uifactory.addFormSubmitButton("save", buttonsCont);
+	}
+
+	@Override
+	protected void doDispose() {
+		//
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		Collection<String> selectedKeys = accessEl.getSelectedKeys();
+		config.setBooleanEntry(AdobeConnectEditController.ACCESS_BY_DATES, selectedKeys.contains(accessKeys[0]));
+		config.setBooleanEntry(AdobeConnectEditController.GUEST_ACCESS_ALLOWED, selectedKeys.contains(accessKeys[1]));
+		config.setBooleanEntry(AdobeConnectEditController.MODERATOR_START_MEETING, selectedKeys.contains(accessKeys[2]));
+		fireEvent(ureq, NodeEditController.NODECONFIG_CHANGED_EVENT);
+	}
+}
diff --git a/src/main/java/org/olat/course/nodes/adobeconnect/AdobeConnectEditController.java b/src/main/java/org/olat/course/nodes/adobeconnect/AdobeConnectEditController.java
index 052c52d51e9..0a41a654fcb 100644
--- a/src/main/java/org/olat/course/nodes/adobeconnect/AdobeConnectEditController.java
+++ b/src/main/java/org/olat/course/nodes/adobeconnect/AdobeConnectEditController.java
@@ -22,6 +22,7 @@ package org.olat.course.nodes.adobeconnect;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.Component;
 import org.olat.core.gui.components.tabbedpane.TabbedPane;
+import org.olat.core.gui.components.velocity.VelocityContainer;
 import org.olat.core.gui.control.Controller;
 import org.olat.core.gui.control.ControllerEventListener;
 import org.olat.core.gui.control.Event;
@@ -34,6 +35,7 @@ import org.olat.course.condition.ConditionEditController;
 import org.olat.course.editor.NodeEditController;
 import org.olat.course.nodes.AdobeConnectCourseNode;
 import org.olat.course.run.userview.UserCourseEnvironment;
+import org.olat.modules.ModuleConfiguration;
 
 /**
  * 
@@ -42,7 +44,8 @@ import org.olat.course.run.userview.UserCourseEnvironment;
  *
  */
 public class AdobeConnectEditController extends ActivateableTabbableDefaultController implements ControllerEventListener {
-	
+
+	public static final String ACCESS_BY_DATES = "accessByDates";
 	public static final String GUEST_ACCESS_ALLOWED = "guestAccessAllowed";
 	public static final String MODERATOR_START_MEETING = "moderatorStartMeeting";
 	
@@ -51,17 +54,21 @@ public class AdobeConnectEditController extends ActivateableTabbableDefaultContr
 	private static final String[] paneKeys = { PANE_TAB_VCCONFIG, PANE_TAB_ACCESSIBILITY };
 	
 	private TabbedPane tabPane;
-	
+	private final VelocityContainer myContent;
+
+	private AdobeConnectConfigForm configCtrl;
 	private ConditionEditController accessibilityCondContr;
 	
+	private final ModuleConfiguration config;
 	private final AdobeConnectCourseNode courseNode;
 	
 	public AdobeConnectEditController(UserRequest ureq, WindowControl wControl, AdobeConnectCourseNode courseNode,
 			ICourse course, UserCourseEnvironment userCourseEnv) {
 		super(ureq, wControl);
 		this.courseNode = courseNode;
+		config = courseNode.getModuleConfiguration();	
 		
-		String providerId = courseNode.getModuleConfiguration().getStringValue("vc_provider_id");
+		String providerId = config.getStringValue("vc_provider_id");
 		if("wimba".equals(providerId)) {
 			showWarning("wimba.not.supported.message");
 		}
@@ -70,6 +77,12 @@ public class AdobeConnectEditController extends ActivateableTabbableDefaultContr
 		accessibilityCondContr = new ConditionEditController(ureq, wControl, userCourseEnv,
 				accessCondition, AssessmentHelper.getAssessableNodes(course.getEditorTreeModel(), courseNode));
 		listenTo(accessibilityCondContr);
+		
+		myContent = createVelocityContainer("edit");
+		
+		configCtrl = new AdobeConnectConfigForm(ureq, getWindowControl(), config);
+		listenTo(configCtrl);
+		myContent.put("configuration", configCtrl.getInitialComponent());
 	}
 	
 	@Override
@@ -100,6 +113,12 @@ public class AdobeConnectEditController extends ActivateableTabbableDefaultContr
 				courseNode.setPreConditionAccess(cond);
 				fireEvent(ureq, NodeEditController.NODECONFIG_CHANGED_EVENT);
 			}
+		} else if (source == configCtrl) {
+			if (event == Event.CANCELLED_EVENT) {
+				// do nothing
+			} else if (event == Event.DONE_EVENT || event == NodeEditController.NODECONFIG_CHANGED_EVENT) {
+				fireEvent(ureq, NodeEditController.NODECONFIG_CHANGED_EVENT);
+			}
 		}
 	}
 	
@@ -108,5 +127,6 @@ public class AdobeConnectEditController extends ActivateableTabbableDefaultContr
 		tabPane = tabbedPane;
 		tabbedPane.addTab(translate(PANE_TAB_ACCESSIBILITY),
 				accessibilityCondContr.getWrappedDefaultAccessConditionVC(translate("condition.accessibility.title")));
+		tabbedPane.addTab(translate(PANE_TAB_VCCONFIG), myContent);
 	}
 }
diff --git a/src/main/java/org/olat/course/nodes/adobeconnect/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/nodes/adobeconnect/_i18n/LocalStrings_de.properties
index 41298615e0f..3bfb6734b92 100644
--- a/src/main/java/org/olat/course/nodes/adobeconnect/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/course/nodes/adobeconnect/_i18n/LocalStrings_de.properties
@@ -4,5 +4,9 @@ pane.tab.vcconfig=Konfiguration
 condition.accessibility.title=Zugang
 guest.allowed=Zutritt
 moderator.start.meeting=Raum \u00F6ffnen
+vc.access.label=Zugang
+vc.access.start=Nur Moderatoren d\u00fcrfen diesen Raum er\u00f6ffnen
+vc.access.open=Moderator muss im Raum online sein, um Zutritt f\u00fcr Teilnehmer zu best\u00e4tigen
+vc.options.label=Zutrittsberechtigung
 wimba.not.supported.title=Wimba Classroom
 wimba.not.supported.message=Wimba Classroom ist nicht unterscht\u00FCtzt.
diff --git a/src/main/java/org/olat/course/nodes/adobeconnect/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/nodes/adobeconnect/_i18n/LocalStrings_en.properties
index 35246ff0e05..95252b2306f 100644
--- a/src/main/java/org/olat/course/nodes/adobeconnect/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/course/nodes/adobeconnect/_i18n/LocalStrings_en.properties
@@ -3,7 +3,13 @@ condition.accessibility.title=Access
 guest.allowed=Access
 moderator.start.meeting=Room opening
 pane.tab.accessibility=Access
-pane.tab.vcconfig=Room
+pane.tab.vcconfig=Configuration
 title_vc=Adobe Connect
+vc.options.label=Access authorisation
+vc.access.label=Access authorisation
+vc.access.dates=Virtual classroom shall only be available at defined dates
+vc.access.label=Access authorisation
+vc.access.open=Moderator must be in classroom to grant access to users
+vc.access.start=Only moderators are allowed to open this virtual classroom
 wimba.not.supported.message=Wimba Classroom is not supported.
 wimba.not.supported.title=Wimba Classroom
diff --git a/src/main/java/org/olat/course/nodes/adobeconnect/_i18n/LocalStrings_fr.properties b/src/main/java/org/olat/course/nodes/adobeconnect/_i18n/LocalStrings_fr.properties
index cb979a3a027..a720b18df8b 100644
--- a/src/main/java/org/olat/course/nodes/adobeconnect/_i18n/LocalStrings_fr.properties
+++ b/src/main/java/org/olat/course/nodes/adobeconnect/_i18n/LocalStrings_fr.properties
@@ -7,3 +7,7 @@ pane.tab.vcconfig=Configuration
 title_vc=Adobe Connect
 wimba.not.supported.message=Wimba Classroom n'est plus support\u00E9.
 wimba.not.supported.title=Wimba Classroom
+vc.options.label=Autorisation d'acc\u00E8s
+vc.access.label=Autorisation d'acc\u00E8s
+vc.access.open=Les mod\u00E9rateur de la classe doivent \u00EAtre connect\u00E9 pour confirmer l'acc\u00E8s des participants
+vc.access.start=Seuls les mod\u00E9rateurs ont le droit d'ouvrir cette classe
diff --git a/src/main/java/org/olat/course/nodes/adobeconnect/_i18n/LocalStrings_pt_BR.properties b/src/main/java/org/olat/course/nodes/adobeconnect/_i18n/LocalStrings_pt_BR.properties
new file mode 100644
index 00000000000..9a14be3205b
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/adobeconnect/_i18n/LocalStrings_pt_BR.properties
@@ -0,0 +1,6 @@
+#Tue Nov 22 17:51:38 CET 2016
+vc.options.label=Autoriza\u00E7\u00E3o de acesso
+vc.access.dates=A sala de aula virtual s\u00F3 estar\u00E1 dispon\u00EDvel em datas definidas
+vc.access.label=Autoriza\u00E7\u00E3o de acesso
+vc.access.open=O moderador deve estar em sala de aula para conceder acesso aos usu\u00E1rios
+vc.access.start=Somente os moderadores podem abrir esta sala de aula virtual
diff --git a/src/main/java/org/olat/modules/_spring/modulesContext.xml b/src/main/java/org/olat/modules/_spring/modulesContext.xml
index b21c3eecc93..073544232c4 100644
--- a/src/main/java/org/olat/modules/_spring/modulesContext.xml
+++ b/src/main/java/org/olat/modules/_spring/modulesContext.xml
@@ -10,6 +10,7 @@
   
 	<context:component-scan base-package="org.olat.modules"/>
 
+	<import resource="classpath:/org/olat/modules/adobeconnect/_spring/adobeConnectContext.xml"/>
 	<import resource="classpath:/org/olat/modules/coach/_spring/coachContext.xml"/>
 	<import resource="classpath:/org/olat/modules/iq/_spring/iqContext.xml"/>
 	<import resource="classpath:/org/olat/modules/lecture/_spring/lectureContext.xml"/>
@@ -175,26 +176,6 @@
 		</property>
 	</bean>
 	
-	<!-- Adobe Connect admin. panel -->
-	<bean class="org.olat.core.extensions.action.GenericActionExtension" init-method="initExtensionPoints">
-		<property name="order" value="7211" />
-		<property name="actionController">	
-			<bean class="org.olat.core.gui.control.creator.AutoCreator" scope="prototype">
-				<property name="className" value="org.olat.modules.adobeconnect.ui.AdobeConnectAdminController"/>
-			</bean>
-		</property>
-		<property name="navigationKey" value="adobeconnect" />
-		<property name="parentTreeNodeIdentifier" value="externalToolsParent" /> 
-		<property name="i18nActionKey" value="admin.menu.title"/>
-		<property name="i18nDescriptionKey" value="admin.menu.title.alt"/>
-		<property name="translationPackage" value="org.olat.modules.adobeconnect.ui"/>
-		<property name="extensionPoints">
-			<list>	
-				<value>org.olat.admin.SystemAdminMainController</value>		
-			</list>
-		</property>
-	</bean>
-	
 	<!-- OpenMeetings admin. panel -->
 	<bean class="org.olat.core.extensions.action.GenericActionExtension" init-method="initExtensionPoints">
 		<property name="order" value="7210" />
diff --git a/src/main/java/org/olat/modules/adobeconnect/AdobeConnectManager.java b/src/main/java/org/olat/modules/adobeconnect/AdobeConnectManager.java
index 435521febc3..352de5556bd 100644
--- a/src/main/java/org/olat/modules/adobeconnect/AdobeConnectManager.java
+++ b/src/main/java/org/olat/modules/adobeconnect/AdobeConnectManager.java
@@ -24,6 +24,7 @@ import java.util.List;
 import java.util.Locale;
 
 import org.olat.core.id.Identity;
+import org.olat.course.nodes.adobeconnect.compatibility.MeetingCompatibilityDate;
 import org.olat.group.BusinessGroup;
 import org.olat.modules.adobeconnect.model.AdobeConnectErrors;
 import org.olat.modules.adobeconnect.model.AdobeConnectSco;
@@ -39,11 +40,22 @@ public interface AdobeConnectManager {
 	
 	public boolean checkConnection(String url, String login, String password, AdobeConnectErrors error);
 	
-	public void createMeeting(String name, String description, String templateId,
-			Date start, Date end, Locale locale, boolean allAccess,
+	public void createMeeting(String name, String description, String templateId, boolean permanent,
+			Date start, long leadTime, Date end, long followupTime, Locale locale, boolean allAccess,
 			RepositoryEntry entry, String subIdent, BusinessGroup businessGroup,
 			Identity actingIdentity, AdobeConnectErrors error);
 	
+	/**
+	 * The method creates only the Adobe Connect Meeting without membership.
+	 * 
+	 * @param meeting The meeting reference
+	 * @param locale The locale
+	 * @param allAccess If guests has access without confirmation of the moderator
+	 * @param error The error collector
+	 * @return An updated meeting reference
+	 */
+	public AdobeConnectMeeting createAdobeMeeting(AdobeConnectMeeting meeting, Locale locale, boolean allAccess, AdobeConnectErrors error);
+	
 	/**
 	 * 
 	 * @param meeting The meeting to update
@@ -53,11 +65,23 @@ public interface AdobeConnectManager {
 	 * @param endDate The end date to update or null
 	 * @param error
 	 */
-	public AdobeConnectMeeting updateMeeting(AdobeConnectMeeting meeting, String name, String description,
-			String templateId, Date startDate, Date endDate, AdobeConnectErrors error);
+	public AdobeConnectMeeting updateMeeting(AdobeConnectMeeting meeting, String name, String description, String templateId,
+			boolean permanent, Date startDate, long leadTime, Date endDate, long followupTime, AdobeConnectErrors error);
 	
 	public AdobeConnectMeeting shareDocuments(AdobeConnectMeeting meeting, List<AdobeConnectSco> documents);
 	
+	public void convert(List<MeetingCompatibilityDate> meetings, RepositoryEntry entry, String subIdent);
+	
+	/**
+	 * Reload the meeting.
+	 * 
+	 * @param meeting The meeting to reload.
+	 * @return A fresh meeting object
+	 */
+	public AdobeConnectMeeting getMeeting(AdobeConnectMeeting meeting);
+	
+	public List<AdobeConnectMeeting> getMeetingsBefore(Date date);
+	
 	public boolean deleteMeeting(AdobeConnectMeeting meeting, AdobeConnectErrors error);
 	
 	public List<AdobeConnectSco> getTemplates();
@@ -73,6 +97,8 @@ public interface AdobeConnectManager {
 	 */
 	public List<AdobeConnectMeeting> getMeetings(RepositoryEntry entry, String subIdent, BusinessGroup businessGroup);
 	
+	public boolean hasMeetings(RepositoryEntry entry, String subIdent, BusinessGroup businessGroup);
+	
 	public List<AdobeConnectMeeting> getAllMeetings();
 	
 	/**
@@ -96,6 +122,9 @@ public interface AdobeConnectManager {
 	public boolean registerFor(AdobeConnectMeeting meeting, Identity identity,
 			AdobeConnectMeetingPermission permission, AdobeConnectErrors error);
 	
+
+	public String open(AdobeConnectMeeting meeting, Identity identity, AdobeConnectErrors error);
+	
 	public String join(AdobeConnectMeeting meeting, Identity identity, AdobeConnectErrors error);
 	
 	public String linkTo(AdobeConnectSco content, Identity identity, AdobeConnectErrors error);
diff --git a/src/main/java/org/olat/modules/adobeconnect/AdobeConnectMeeting.java b/src/main/java/org/olat/modules/adobeconnect/AdobeConnectMeeting.java
index c4b12626434..1eed5c4d42d 100644
--- a/src/main/java/org/olat/modules/adobeconnect/AdobeConnectMeeting.java
+++ b/src/main/java/org/olat/modules/adobeconnect/AdobeConnectMeeting.java
@@ -45,18 +45,42 @@ public interface AdobeConnectMeeting extends ModifiedInfo, CreateInfo {
 	
 	public void setDescription(String description);
 	
+	public boolean isPermanent();
+
+	public void setPermanent(boolean permanent);
+	
 	public Date getStartDate();
 	
 	public void setStartDate(Date date);
 	
+	public long getLeadTime();
+
+	public void setLeadTime(long leadTime);
+	
+	public Date getStartWithLeadTime();
+	
 	public Date getEndDate();
 	
 	public void setEndDate(Date date);
 	
+	public long getFollowupTime();
+
+	public void setFollowupTime(long followupTime);
+	
+	public Date getEndWithFollowupTime();
+	
+	public boolean isOpened();
+	
+	public void setOpened(boolean open);
+
 	public String getScoId();
 	
 	public String getFolderId();
 	
+	public String getTemplateId();
+	
+	public void setTemplateId(String templateId);
+	
 	public List<String> getSharedDocumentIds();
 	
 	public void setSharedDocumentIds(List<String> ids);
diff --git a/src/main/java/org/olat/modules/adobeconnect/AdobeConnectModule.java b/src/main/java/org/olat/modules/adobeconnect/AdobeConnectModule.java
index 38cda7fdeab..308343be468 100644
--- a/src/main/java/org/olat/modules/adobeconnect/AdobeConnectModule.java
+++ b/src/main/java/org/olat/modules/adobeconnect/AdobeConnectModule.java
@@ -50,7 +50,11 @@ public class AdobeConnectModule extends AbstractSpringModule implements ConfigOn
 	private static final String ADMIN_CRED = "vc.adobe.adminpassword";
 	private static final String ACCOUNTID = "vc.adobe.accountid";
 	private static final String PROVIDERID = "vc.adobe.providerid";
-	
+	private static final String CLEAN_MEETINGS = "vc.adobe.cleanupMeetings";
+	private static final String DAYS_TO_KEEP = "vc.adobe.daysToKeep";
+	private static final String SINGLE_MEETING_MODE = "vc.adobe.single.meeting.mode";
+	private static final String CREATE_MEETING_IMMEDIATELY = "vc.adobe.createMeetingImmediately";
+
 	@Value("${vc.adobe.enabled}")
 	private boolean enabled;
 	@Value("${vc.adobe.protocol:https}")
@@ -73,6 +77,16 @@ public class AdobeConnectModule extends AbstractSpringModule implements ConfigOn
 	private String accountId;
 	@Value("${vc.adobe.provider:connect9}")
 	private String providerId;
+	@Value("${vc.adobe.cleanupMeetings:false}")
+	private String cleanupMeetings;
+	@Value("${vc.adobe.daysToKeep:}")
+	private String daysToKeep;
+	@Value("${vc.adobe.single.meeting.mode:true}")
+	private String singleMeetingMode;
+	@Value("${vc.adobe.createMeetingImmediately:true}")
+	private String createMeetingImmediately;
+	@Value("${vc.adobe.login.compatibility.mode:false}")
+	private String loginCompatibilityMode;
 	
 	@Autowired
 	public AdobeConnectModule(CoordinatorManager coordinatorManager) {
@@ -98,6 +112,10 @@ public class AdobeConnectModule extends AbstractSpringModule implements ConfigOn
 		contextPath = getStringPropertyValue(CONTEXTPATH, contextPath);
 		accountId = getStringPropertyValue(ACCOUNTID, accountId);
 		providerId = getStringPropertyValue(PROVIDERID, providerId);
+		cleanupMeetings = getStringPropertyValue(CLEAN_MEETINGS, cleanupMeetings);
+		daysToKeep = getStringPropertyValue(DAYS_TO_KEEP, daysToKeep);
+		singleMeetingMode = getStringPropertyValue(SINGLE_MEETING_MODE, singleMeetingMode);
+		createMeetingImmediately = getStringPropertyValue(CREATE_MEETING_IMMEDIATELY, createMeetingImmediately);
 	}
 
 	@Override
@@ -243,11 +261,62 @@ public class AdobeConnectModule extends AbstractSpringModule implements ConfigOn
 	}
 
 	public String getAccountId() {
+		if("-".equals(accountId)) {
+			return null;
+		}
 		return accountId;
 	}
 
 	public void setAccountId(String accountId) {
 		this.accountId = accountId;
-		setStringProperty(ACCOUNTID, accountId, true);
+		setStringProperty(ACCOUNTID, StringHelper.containsNonWhitespace(accountId) ? accountId : "-" , true);
+	}
+
+	public boolean isCleanupMeetings() {
+		return "true".equals(cleanupMeetings);
+	}
+
+	public void setCleanupMeetings(boolean enable) {
+		cleanupMeetings = enable ? "true" : "false";
+		setStringProperty(CLEAN_MEETINGS, cleanupMeetings, true);
+	}
+
+	public long getDaysToKeep() {
+		if(StringHelper.isLong(daysToKeep)) {
+			return Long.parseLong(daysToKeep);
+		}
+		return -1;
+	}
+
+	public void setDaysToKeep(String daysToKeep) {
+		this.daysToKeep = daysToKeep;
+		setStringProperty(DAYS_TO_KEEP, daysToKeep, true);
+	}
+
+	public boolean isSingleMeetingMode() {
+		return "true".equals(singleMeetingMode);
+	}
+
+	public void setSingleMeetingMode(boolean enable) {
+		singleMeetingMode = enable ? "true" : "false";
+		setStringProperty(SINGLE_MEETING_MODE, singleMeetingMode, true);
+	}
+
+	public boolean isCreateMeetingImmediately() {
+		return "true".equals(createMeetingImmediately);
+	}
+
+	public void setCreateMeetingImmediately(boolean enable) {
+		createMeetingImmediately = enable ? "true" : "false";
+		setStringProperty(CREATE_MEETING_IMMEDIATELY, createMeetingImmediately, true);
+	}
+
+	/**
+	 * 
+	 * @return true if the app. needs to be compatible with the
+	 * 		login and password format of the old implementation.
+	 */
+	public boolean isLoginCompatibilityMode() {
+		return "true".equals(loginCompatibilityMode);
 	}
 }
diff --git a/src/main/java/org/olat/modules/adobeconnect/_spring/adobeConnectContext.xml b/src/main/java/org/olat/modules/adobeconnect/_spring/adobeConnectContext.xml
new file mode 100644
index 00000000000..eea808023ce
--- /dev/null
+++ b/src/main/java/org/olat/modules/adobeconnect/_spring/adobeConnectContext.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xmlns:context="http://www.springframework.org/schema/context" 
+	xsi:schemaLocation="
+  http://www.springframework.org/schema/beans 
+  http://www.springframework.org/schema/beans/spring-beans.xsd 
+  http://www.springframework.org/schema/context 
+  http://www.springframework.org/schema/context/spring-context.xsd">
+  
+	<bean class="org.olat.core.extensions.action.GenericActionExtension" init-method="initExtensionPoints">
+		<property name="order" value="7211" />
+		<property name="actionController">	
+			<bean class="org.olat.core.gui.control.creator.AutoCreator" scope="prototype">
+				<property name="className" value="org.olat.modules.adobeconnect.ui.AdobeConnectAdminController"/>
+			</bean>
+		</property>
+		<property name="navigationKey" value="adobeconnect" />
+		<property name="parentTreeNodeIdentifier" value="externalToolsParent" /> 
+		<property name="i18nActionKey" value="admin.menu.title"/>
+		<property name="i18nDescriptionKey" value="admin.menu.title.alt"/>
+		<property name="translationPackage" value="org.olat.modules.adobeconnect.ui"/>
+		<property name="extensionPoints">
+			<list>	
+				<value>org.olat.admin.SystemAdminMainController</value>		
+			</list>
+		</property>
+	</bean>
+	
+	<bean id="adobeCleanupTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
+    	<property name="jobDetail" ref="adobeConnectCleanupJob" />
+    	<property name="cronExpression" value="0 32 2 * * ?"/>
+    	<property name="startDelay" value="45000" />
+	</bean>
+	
+	<bean id="adobeConnectCleanupJob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean" lazy-init="true">
+		<property name="jobClass" value="org.olat.modules.adobeconnect.manager.AdobeConnectCleanupJob" />
+	</bean>
+	
+</beans>
\ No newline at end of file
diff --git a/src/main/java/org/olat/modules/adobeconnect/manager/AbstractAdobeConnectProvider.java b/src/main/java/org/olat/modules/adobeconnect/manager/AbstractAdobeConnectProvider.java
index 715479ee5a2..da82eec5a19 100644
--- a/src/main/java/org/olat/modules/adobeconnect/manager/AbstractAdobeConnectProvider.java
+++ b/src/main/java/org/olat/modules/adobeconnect/manager/AbstractAdobeConnectProvider.java
@@ -167,6 +167,16 @@ public abstract class AbstractAdobeConnectProvider implements AdobeConnectSPI {
 		return sendScoRequest(builder, errors);
 	}
 
+	@Override
+	public List<AdobeConnectSco> getMeetingByName(String name, AdobeConnectErrors errors) {
+		UriBuilder builder = adobeConnectModule.getAdobeConnectUriBuilder();
+		builder
+			.queryParam("action", "sco-search-by-field")
+			.queryParam("query", PREFIX + name)
+			.queryParam("filter-type", "meeting");
+		return sendScoRequest(builder, errors);
+	}
+
 	@Override
 	public boolean updateScoMeeting(String scoId, String name, String description, String templateId,
 			Date startDate, Date endDate, AdobeConnectErrors errors) {
@@ -476,6 +486,7 @@ public abstract class AbstractAdobeConnectProvider implements AdobeConnectSPI {
 
 					AdobeConnectSco connectSco = new AdobeConnectSco();
 					connectSco.setScoId(sco.getAttribute("sco-id"));
+					connectSco.setFolderId(sco.getAttribute("folder-id"));
 					connectSco.setType(sco.getAttribute("type"));
 					connectSco.setIcon(sco.getAttribute("icon"));
 					String urlPath = AdobeConnectUtils.getFirstElementValue(sco, "url-path");
@@ -532,11 +543,15 @@ public abstract class AbstractAdobeConnectProvider implements AdobeConnectSPI {
 		}
 		
 		UriBuilder builder = adobeConnectModule.getAdobeConnectUriBuilder();
-		URI uri = builder
+		builder = builder
 			.queryParam("action", "login")
 			.queryParam("login", adobeConnectModule.getAdminLogin())
 			.queryParam("password", adobeConnectModule.getAdminPassword())
-			.queryParam("session", session.getSession())
+			.queryParam("session", session.getSession());
+		if(StringHelper.containsNonWhitespace(adobeConnectModule.getAccountId())) {
+			builder = builder.queryParam("account-id", adobeConnectModule.getAccountId());
+		}
+		URI uri = builder
 			.build();
 		
 		HttpGet getLogin = new HttpGet(uri);
@@ -653,6 +668,8 @@ public abstract class AbstractAdobeConnectProvider implements AdobeConnectSPI {
 					permission.setPermissionId(permissionEl.getAttribute("permission-id"));
 					permissions.add(permission);
 				}
+			} else if(AdobeConnectUtils.isStatusNoData(doc)) {
+				// ok, there isn't any permissions
 			} else {
 				AdobeConnectUtils.error(doc, errors);
 			}
diff --git a/src/main/java/org/olat/modules/adobeconnect/manager/AdobeConnect9Provider.java b/src/main/java/org/olat/modules/adobeconnect/manager/AdobeConnect9Provider.java
index 5ad09e7092f..dcb2d09f88f 100644
--- a/src/main/java/org/olat/modules/adobeconnect/manager/AdobeConnect9Provider.java
+++ b/src/main/java/org/olat/modules/adobeconnect/manager/AdobeConnect9Provider.java
@@ -114,13 +114,16 @@ public class AdobeConnect9Provider extends AbstractAdobeConnectProvider {
 			}
 			return null;
 		}
-		
+
 		String login = Encoder.decrypt(authentication.getCredential(), authentication.getSalt(), Encoder.Algorithm.aes);
 		UriBuilder builder = adobeConnectModule.getAdobeConnectUriBuilder();
 		builder
 			.queryParam("action", "login")
 			.queryParam("login", authentication.getAuthusername())
 			.queryParam("password", login);
+		if(StringHelper.containsNonWhitespace(adobeConnectModule.getAccountId())) {
+			builder = builder.queryParam("account-id", adobeConnectModule.getAccountId());
+		}
 		
 		URI uri = builder.build();
 		BreezeSession session = null;
diff --git a/src/main/java/org/olat/modules/adobeconnect/manager/AdobeConnectCleanupJob.java b/src/main/java/org/olat/modules/adobeconnect/manager/AdobeConnectCleanupJob.java
new file mode 100644
index 00000000000..f98bec75955
--- /dev/null
+++ b/src/main/java/org/olat/modules/adobeconnect/manager/AdobeConnectCleanupJob.java
@@ -0,0 +1,77 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.adobeconnect.manager;
+
+import java.util.Calendar;
+import java.util.List;
+
+import org.apache.logging.log4j.Logger;
+import org.olat.commons.calendar.CalendarUtils;
+import org.olat.core.CoreSpringFactory;
+import org.olat.core.commons.services.scheduler.JobWithDB;
+import org.olat.core.logging.Tracing;
+import org.olat.modules.adobeconnect.AdobeConnectManager;
+import org.olat.modules.adobeconnect.AdobeConnectMeeting;
+import org.olat.modules.adobeconnect.AdobeConnectModule;
+import org.olat.modules.adobeconnect.model.AdobeConnectErrors;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+
+/**
+ * 
+ * Initial date: 4 juil. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class AdobeConnectCleanupJob extends JobWithDB {
+	
+	private static final Logger log = Tracing.createLoggerFor(AdobeConnectCleanupJob.class);
+	
+	@Override
+	public void executeWithDB(JobExecutionContext arg0) throws JobExecutionException {
+		AdobeConnectModule adobeConnectModule = CoreSpringFactory.getImpl(AdobeConnectModule.class);
+		if(adobeConnectModule.isCleanupMeetings() && adobeConnectModule.getDaysToKeep() > 0) {
+			cleanUp(adobeConnectModule.getDaysToKeep());
+		}
+	}
+	
+	private void cleanUp(long days) {
+		Calendar cal = Calendar.getInstance();
+		cal.add(Calendar.DATE, -(int)days);
+		CalendarUtils.getStartOfDay(cal);
+
+		AdobeConnectManager adobeConnectManager = CoreSpringFactory.getImpl(AdobeConnectManager.class);
+		List<AdobeConnectMeeting> oldMeetings = adobeConnectManager.getMeetingsBefore(cal.getTime());
+		for(AdobeConnectMeeting oldMeeting:oldMeetings) {
+			try {
+				AdobeConnectErrors errors = new AdobeConnectErrors();
+				adobeConnectManager.deleteMeeting(oldMeeting, errors);
+				if(errors.hasErrors()) {
+					log.error("Error trying to delete adobe connect meeting with id {} with message: {}",
+							(oldMeeting != null && oldMeeting.getKey() != null ? oldMeeting.getKey() : "NULL"),
+							errors.getErrorMessages());
+				}
+			} catch (Exception e) {
+				log.error("", e);
+			}
+		}
+	}
+
+}
diff --git a/src/main/java/org/olat/modules/adobeconnect/manager/AdobeConnectManagerImpl.java b/src/main/java/org/olat/modules/adobeconnect/manager/AdobeConnectManagerImpl.java
index 4eaf57600c7..54b01378899 100644
--- a/src/main/java/org/olat/modules/adobeconnect/manager/AdobeConnectManagerImpl.java
+++ b/src/main/java/org/olat/modules/adobeconnect/manager/AdobeConnectManagerImpl.java
@@ -39,11 +39,14 @@ import org.apache.http.util.EntityUtils;
 import org.apache.logging.log4j.Logger;
 import org.olat.basesecurity.Authentication;
 import org.olat.basesecurity.BaseSecurity;
+import org.olat.core.commons.persistence.DB;
+import org.olat.core.helpers.Settings;
 import org.olat.core.id.Identity;
 import org.olat.core.logging.Tracing;
 import org.olat.core.util.Encoder;
 import org.olat.core.util.StringHelper;
 import org.olat.core.util.WebappHelper;
+import org.olat.course.nodes.adobeconnect.compatibility.MeetingCompatibilityDate;
 import org.olat.group.BusinessGroup;
 import org.olat.group.DeletableGroupData;
 import org.olat.modules.adobeconnect.AdobeConnectManager;
@@ -52,6 +55,7 @@ import org.olat.modules.adobeconnect.AdobeConnectMeetingPermission;
 import org.olat.modules.adobeconnect.AdobeConnectModule;
 import org.olat.modules.adobeconnect.AdobeConnectUser;
 import org.olat.modules.adobeconnect.model.AdobeConnectErrors;
+import org.olat.modules.adobeconnect.model.AdobeConnectMeetingImpl;
 import org.olat.modules.adobeconnect.model.AdobeConnectPrincipal;
 import org.olat.modules.adobeconnect.model.AdobeConnectSco;
 import org.olat.modules.adobeconnect.model.BreezeSession;
@@ -76,6 +80,8 @@ public class AdobeConnectManagerImpl implements AdobeConnectManager, DeletableGr
 
 	private AdobeConnectSPI adapter;
 	
+	@Autowired
+	private DB dbInstance;
 	@Autowired
 	private UserManager userManager;
 	@Autowired
@@ -101,6 +107,17 @@ public class AdobeConnectManagerImpl implements AdobeConnectManager, DeletableGr
 		return adapter == null ? new NoAdapterProvider() : adapter;
 	}
 
+	@Override
+	public AdobeConnectMeeting getMeeting(AdobeConnectMeeting meeting) {
+		if(meeting == null || meeting.getKey() == null) return meeting;
+		return adobeConnectMeetingDao.loadByKey(meeting.getKey());
+	}
+
+	@Override
+	public List<AdobeConnectMeeting> getMeetingsBefore(Date date) {
+		return adobeConnectMeetingDao.getMeetingsBefore(date);
+	}
+
 	@Override
 	public void deleteUserData(Identity identity, String newDeletedUserName) {
 		adobeConnectUserDao.deleteAdobeConnectUser(identity);
@@ -110,11 +127,11 @@ public class AdobeConnectManagerImpl implements AdobeConnectManager, DeletableGr
 	public boolean deleteGroupDataFor(BusinessGroup group) {
 		List<AdobeConnectMeeting> meetings = adobeConnectMeetingDao.getMeetings(group);
 		
-		AdobeConnectErrors erros = new AdobeConnectErrors();
+		AdobeConnectErrors errors = new AdobeConnectErrors();
 		for(AdobeConnectMeeting meeting:meetings) {
-			deleteMeeting(meeting, erros);
+			deleteMeeting(meeting, errors);
 		}
-		return erros.hasErrors();
+		return errors.hasErrors();
 	}
 
 	private String generateFolderName(RepositoryEntry entry, String subIdent, BusinessGroup businessGroup) {
@@ -133,8 +150,73 @@ public class AdobeConnectManagerImpl implements AdobeConnectManager, DeletableGr
 	}
 
 	@Override
-	public void createMeeting(String name, String description, String templateId,
-			Date start, Date end, Locale locale, boolean allAccess,
+	public void createMeeting(String name, String description, String templateId, boolean permanent,
+			Date start, long leadtime, Date end, long followupTime, Locale locale, boolean allAccess,
+			RepositoryEntry entry, String subIdent, BusinessGroup businessGroup,
+			Identity actingIdentity, AdobeConnectErrors errors) {
+		if(adobeConnectModule.isCreateMeetingImmediately()) {
+			createAdobeMeeting(name, description, templateId, permanent, start, leadtime, end, followupTime, locale,
+					allAccess, entry, subIdent, businessGroup, actingIdentity, errors);
+		} else {
+			adobeConnectMeetingDao.createMeeting(name, description, permanent,
+					start, leadtime, end, followupTime, templateId, null, null, null, entry, subIdent, businessGroup);
+		}
+	}
+	
+	@Override
+	public AdobeConnectMeeting createAdobeMeeting(AdobeConnectMeeting meeting, Locale locale, boolean allAccess, AdobeConnectErrors errors) {
+		AdobeConnectSco sco = null;
+		if(adobeConnectModule.isSingleMeetingMode()) {
+			sco = getSingleMeetingRoom(meeting.getEntry(), meeting.getSubIdent(), meeting.getBusinessGroup(), errors);
+		} else if(meeting.isPermanent()) {
+			sco = getPermanentMeetingRoom(meeting.getEntry(), meeting.getSubIdent(), meeting.getBusinessGroup(), errors);
+		}
+		if(sco == null) {
+			AdobeConnectSco folder;
+			String folderName = generateFolderName(meeting.getEntry(), meeting.getSubIdent(), meeting.getBusinessGroup());
+			List<AdobeConnectSco> folderScos = getAdapter().getFolderByName(folderName, errors);
+			if(folderScos == null || folderScos.isEmpty()) {
+				folder = getAdapter().createFolder(folderName, errors);
+			} else {
+				folder = folderScos.get(0);
+			}
+
+			if(!errors.hasErrors()) {
+				sco = getAdapter().createScoMeeting(meeting.getName(), meeting.getDescription(), folder.getScoId(),
+						meeting.getTemplateId(), meeting.getStartDate(), meeting.getEndDate(), locale, errors);	
+			}
+		}
+		if(sco != null) {
+			((AdobeConnectMeetingImpl)meeting).setFolderId(sco.getFolderId());
+			((AdobeConnectMeetingImpl)meeting).setScoId(sco.getScoId());
+			((AdobeConnectMeetingImpl)meeting).setEnvName(adobeConnectModule.getBaseUrl());
+			meeting = adobeConnectMeetingDao.updateMeeting(meeting);
+		}
+		dbInstance.commit();
+		return meeting;
+	}
+
+	/**
+	 * Create the Adobe Meeting first and the OpenOlat database object only
+	 * if the meeting was successfully scheduled.
+	 * 
+	 * @param name The name of the meeting
+	 * @param description The description of the meeting
+	 * @param templateId The template
+	 * @param start Date to start the meeting
+	 * @param leadtime Preparation time
+	 * @param end End of the meeting
+	 * @param followupTime Follow-up time
+	 * @param locale Language
+	 * @param allAccess 
+	 * @param entry The course
+	 * @param subIdent The course node identifier (for example)
+	 * @param businessGroup The business group
+	 * @param actingIdentity The user which create the meeting
+	 * @param errors Errors
+	 */
+	private void createAdobeMeeting(String name, String description, String templateId, boolean permanent,
+			Date start, long leadtime, Date end, long followupTime, Locale locale, boolean allAccess,
 			RepositoryEntry entry, String subIdent, BusinessGroup businessGroup,
 			Identity actingIdentity, AdobeConnectErrors errors) {
 		
@@ -151,13 +233,17 @@ public class AdobeConnectManagerImpl implements AdobeConnectManager, DeletableGr
 			return;// we need a folder
 		}
 		
-		AdobeConnectSco sco = getAdapter().createScoMeeting(name, description, folder.getScoId(), templateId, start, end, locale, errors);
+		AdobeConnectSco sco = null;
+		if(adobeConnectModule.isSingleMeetingMode()) {
+			sco = getSingleMeetingRoom(entry, subIdent, businessGroup, errors);
+		} else if(permanent) {
+			sco = getPermanentMeetingRoom(entry, subIdent, businessGroup, errors);
+		}
+		if(sco == null) {
+			sco = getAdapter().createScoMeeting(name, description, folder.getScoId(), templateId, start, end, locale, errors);
+		}
 		if(sco != null) {
-			getAdapter().setPermissions(sco.getScoId(), true, errors);
-			AdobeConnectPrincipal admin = getAdapter().adminCommonInfo(errors);
-			if(admin != null) {
-				getAdapter().setMember(sco.getScoId(), admin.getPrincipalId(), AdobeConnectMeetingPermission.host.permission(), errors);
-			}
+			getAdapter().setPermissions(sco.getScoId(), allAccess, errors);
 
 			String actingUser = getOrCreateUser(actingIdentity, true, errors);
 			if(actingUser != null) {
@@ -165,8 +251,8 @@ public class AdobeConnectManagerImpl implements AdobeConnectManager, DeletableGr
 			}
 			
 			// try harder if the meeting hasn't a single host
-			if(actingUser == null && admin == null) {
-				admin = getAdapter().getPrincipalByLogin(adobeConnectModule.getAdminLogin(), errors);
+			if(actingUser == null) {
+				AdobeConnectPrincipal admin = getAdapter().getPrincipalByLogin(adobeConnectModule.getAdminLogin(), errors);
 				if(admin != null) {
 					getAdapter().setMember(sco.getScoId(), admin.getPrincipalId(), AdobeConnectMeetingPermission.host.permission(), errors);
 				}
@@ -174,24 +260,70 @@ public class AdobeConnectManagerImpl implements AdobeConnectManager, DeletableGr
 			
 			String scoId = sco.getScoId();
 			String envName = adobeConnectModule.getBaseUrl();
-			adobeConnectMeetingDao.createMeeting(name, description, start, end, scoId, folder.getScoId(), envName, entry, subIdent, businessGroup);
+			adobeConnectMeetingDao.createMeeting(name, description,
+					permanent, start, leadtime, end, followupTime,
+					templateId, scoId, folder.getScoId(), envName, entry, subIdent, businessGroup);
+		}
+	}
+	
+	/**
+	 * Search for a meeting room in single meeting mode.
+	 * 
+	 * @return The meeting or null if not found.
+	 */
+	private AdobeConnectSco getSingleMeetingRoom(RepositoryEntry entry, String subIdent, BusinessGroup businessGroup,
+			AdobeConnectErrors errors) {
+		List<AdobeConnectMeeting> currentMeetings = getMeetings(entry, subIdent, businessGroup);	
+		if(currentMeetings != null && !currentMeetings.isEmpty()) {
+			AdobeConnectMeeting meeting = currentMeetings.get(0);
+			return getAdapter().getScoMeeting(meeting, errors);
+		}
+		return null;
+	}
+	
+	private AdobeConnectSco getPermanentMeetingRoom(RepositoryEntry entry, String subIdent, BusinessGroup businessGroup,
+			AdobeConnectErrors errors) {
+		List<AdobeConnectMeeting> currentMeetings = getMeetings(entry, subIdent, businessGroup);
+		if(currentMeetings != null && !currentMeetings.isEmpty()) {
+			for(AdobeConnectMeeting meeting:currentMeetings) {
+				if(meeting.isPermanent()) {
+					return getAdapter().getScoMeeting(meeting, errors);
+				}
+			}
 		}
+		return null;
 	}
 	
 	@Override
 	public AdobeConnectMeeting updateMeeting(AdobeConnectMeeting meeting, String name, String description, String templateId,
-			Date start, Date end, AdobeConnectErrors errors) {
-		boolean ok = getAdapter().updateScoMeeting(meeting.getScoId(), name, description, templateId, start, end, errors);
-		if(ok) {
-			meeting.setName(name);
-			meeting.setDescription(description);
-			meeting.setStartDate(start);
-			meeting.setEndDate(end);
-			meeting = adobeConnectMeetingDao.updateMeeting(meeting);
+			boolean permanent, Date start, long leadTime, Date end, long followupTime, AdobeConnectErrors errors) {
+		if(StringHelper.containsNonWhitespace(meeting.getScoId())) {
+			boolean ok = getAdapter().updateScoMeeting(meeting.getScoId(), name, description, templateId, start, end, errors);
+			if(ok) {
+				meeting = updateMeeting(meeting, name, description, permanent, start, leadTime, end, followupTime);
+			}
+		} else {
+			meeting = updateMeeting(meeting, name, description, permanent, start, leadTime, end, followupTime);
 		}
 		return meeting;
 	}
 	
+	private AdobeConnectMeeting updateMeeting(AdobeConnectMeeting meeting, String name, String description,
+			boolean permanent, Date start, long leadTime, Date end, long followupTime) {
+		meeting.setName(name);
+		meeting.setDescription(description);
+		if(adobeConnectModule.isSingleMeetingMode()) {
+			meeting.setPermanent(true);
+		} else if(!StringHelper.containsNonWhitespace(meeting.getScoId())) {
+			meeting.setPermanent(permanent);
+		}
+		meeting.setStartDate(start);
+		meeting.setLeadTime(leadTime);
+		meeting.setEndDate(end);
+		meeting.setFollowupTime(followupTime);
+		return adobeConnectMeetingDao.updateMeeting(meeting);
+	}
+	
 	@Override
 	public AdobeConnectMeeting shareDocuments(AdobeConnectMeeting meeting, List<AdobeConnectSco> documents) {
 		meeting = adobeConnectMeetingDao.loadByKey(meeting.getKey());
@@ -238,6 +370,14 @@ public class AdobeConnectManagerImpl implements AdobeConnectManager, DeletableGr
 		return registered;
 	}
 
+	@Override
+	public String open(AdobeConnectMeeting meeting, Identity identity, AdobeConnectErrors error) {
+		meeting.setOpened(true);
+		meeting = adobeConnectMeetingDao.updateMeeting(meeting);
+		dbInstance.commit();
+		return join(meeting, identity, error);
+	}
+
 	@Override
 	public String join(AdobeConnectMeeting meeting, Identity identity, AdobeConnectErrors error) {
 		String actingUser = getOrCreateUser(identity, false, error);
@@ -299,15 +439,80 @@ public class AdobeConnectManagerImpl implements AdobeConnectManager, DeletableGr
 	@Override
 	public boolean deleteMeeting(AdobeConnectMeeting meeting, AdobeConnectErrors errors) {
 		boolean deleted = false;
-		AdobeConnectErrors error = new AdobeConnectErrors();
-		if(getAdapter().deleteScoMeeting(meeting, error)) {
+		boolean deleteAdobeConnect = canDeleteAdobeMeeting(meeting);
+		if(deleteAdobeConnect) {
+			AdobeConnectErrors error = new AdobeConnectErrors();
+			if(getAdapter().deleteScoMeeting(meeting, error)) {
+				AdobeConnectMeeting reloadedMeeting = adobeConnectMeetingDao.loadByKey(meeting.getKey());
+				adobeConnectMeetingDao.deleteMeeting(reloadedMeeting);
+				deleted = true;
+			}
+			errors.append(error);
+		} else {
 			AdobeConnectMeeting reloadedMeeting = adobeConnectMeetingDao.loadByKey(meeting.getKey());
 			adobeConnectMeetingDao.deleteMeeting(reloadedMeeting);
 			deleted = true;
 		}
-		errors.append(error);
 		return deleted;
 	}
+	
+	private boolean canDeleteAdobeMeeting(AdobeConnectMeeting meeting) {
+		List<AdobeConnectMeeting> sharedMeetings;
+		if(meeting.getEntry() != null) {
+			sharedMeetings = adobeConnectMeetingDao.getMeetings(meeting.getEntry(), meeting.getSubIdent());
+		} else if(meeting.getBusinessGroup() != null) {
+			sharedMeetings = adobeConnectMeetingDao.getMeetings(meeting.getBusinessGroup());
+		} else {
+			return true;
+		}
+		
+		boolean foundSharedMeeting = false;
+		for(AdobeConnectMeeting sharedMeeting:sharedMeetings) {
+			if(!sharedMeeting.equals(meeting)
+					&& sharedMeeting.getScoId() != null
+					&& sharedMeeting.getScoId().equals(meeting.getScoId())) {
+				foundSharedMeeting |= true;
+			}
+		}
+		return !foundSharedMeeting;
+	}
+
+	@Override
+	public void convert(List<MeetingCompatibilityDate> meetingsData, RepositoryEntry entry, String subIdent) {
+		boolean hasMeetings = adobeConnectMeetingDao.hasMeetings(entry, subIdent);
+		if(hasMeetings) {
+			return; // do the conversion only once
+		}
+		
+		String scoId = null;
+		String folderId = null;
+		String envName = null;
+		
+		AdobeConnectErrors errors = new AdobeConnectErrors();
+		String roomId = entry.getOlatResource().getResourceableId() + "_" + subIdent;
+		List<AdobeConnectSco> meetings = getAdapter().getMeetingByName(roomId, errors);
+		if(meetings != null && !meetings.isEmpty()) {
+			AdobeConnectSco meeting = meetings.get(0);
+			scoId = meeting.getScoId();
+			folderId = meeting.getFolderId();
+			envName = adobeConnectModule.getBaseUrl();
+		}
+
+		for(MeetingCompatibilityDate meetingData:meetingsData) {
+			adobeConnectMeetingDao.createMeeting(meetingData.getTitle(), meetingData.getDescription(), true,
+					meetingData.getStart(), 15, meetingData.getEnd(), 15, null, scoId, folderId, envName, entry, subIdent, null);
+		}
+	}
+
+	@Override
+	public boolean hasMeetings(RepositoryEntry entry, String subIdent, BusinessGroup businessGroup) {
+		if(entry != null) {
+			return adobeConnectMeetingDao.hasMeetings(entry, subIdent);
+		} else if(businessGroup != null) {
+			return adobeConnectMeetingDao.hasMeetings(businessGroup);
+		}
+		return false;
+	}
 
 	@Override
 	public List<AdobeConnectMeeting> getMeetings(RepositoryEntry entry, String subIdent, BusinessGroup businessGroup) {
@@ -328,12 +533,20 @@ public class AdobeConnectManagerImpl implements AdobeConnectManager, DeletableGr
 		String envName = adobeConnectModule.getBaseUrl();
 		AdobeConnectUser user = adobeConnectUserDao.getUser(identity, envName);
 		if(user == null && create) {
-			String login = identity.getUser().getEmail();
+			boolean compatible = adobeConnectModule.isLoginCompatibilityMode();
+			String login;
+			if(compatible) {
+				login = "olat-" + identity.getName();
+			} else {
+				login = identity.getUser().getEmail();
+			}
 			AdobeConnectPrincipal aUser = getAdapter().getPrincipalByLogin(login, error);
 			
 			String creds = null;
 			if(aUser == null) {
-				if(getAdapter().isManagedPassword()) {
+				if(compatible) {
+					creds = Encoder.md5hash(identity.getName() + "@" + Settings.getApplicationName());
+				} else if(getAdapter().isManagedPassword()) {
 					creds = UUID.randomUUID().toString().replace("-", "");
 					if(creds.length() > 32) {
 						creds = creds.substring(0, 32);
diff --git a/src/main/java/org/olat/modules/adobeconnect/manager/AdobeConnectMeetingDAO.java b/src/main/java/org/olat/modules/adobeconnect/manager/AdobeConnectMeetingDAO.java
index 4e9ed51d793..ab5aaf94e95 100644
--- a/src/main/java/org/olat/modules/adobeconnect/manager/AdobeConnectMeetingDAO.java
+++ b/src/main/java/org/olat/modules/adobeconnect/manager/AdobeConnectMeetingDAO.java
@@ -23,6 +23,7 @@ import java.util.Calendar;
 import java.util.Date;
 import java.util.List;
 
+import javax.persistence.TemporalType;
 import javax.persistence.TypedQuery;
 
 import org.olat.core.commons.persistence.DB;
@@ -48,16 +49,19 @@ public class AdobeConnectMeetingDAO {
 	@Autowired
 	private DB dbInstance;
 	
-	public AdobeConnectMeeting createMeeting(String name, String description, Date start, Date end,
-			String scoId, String folderId, String envName,
+	public AdobeConnectMeeting createMeeting(String name, String description,
+			boolean permanent, Date start, long leadTime, Date end, long followupTime,
+			String templateId, String scoId, String folderId, String envName,
 			RepositoryEntry entry, String subIdent, BusinessGroup businessGroup) {
 		AdobeConnectMeetingImpl meeting = new AdobeConnectMeetingImpl();
 		meeting.setCreationDate(new Date());
 		meeting.setLastModified(meeting.getCreationDate());
 		meeting.setName(name);
 		meeting.setDescription(description);
-		meeting.setStartDate(cleanDate(start));
-		meeting.setEndDate(cleanDate(end));
+		meeting.setOpened(false);
+		meeting.setPermanent(permanent);
+		updateDates(meeting, start, leadTime, end, followupTime);
+		meeting.setTemplateId(templateId);
 		meeting.setScoId(scoId);
 		meeting.setFolderId(folderId);
 		meeting.setEnvName(envName);
@@ -74,11 +78,42 @@ public class AdobeConnectMeetingDAO {
 	public AdobeConnectMeeting updateMeeting(AdobeConnectMeeting meeting) {
 		AdobeConnectMeetingImpl meet = (AdobeConnectMeetingImpl)meeting;
 		meet.setLastModified(new Date());
-		meet.setStartDate(cleanDate(meet.getStartDate()));
-		meet.setEndDate(cleanDate(meet.getEndDate()));
+		updateDates(meet, meet.getStartDate(), meet.getLeadTime(), meet.getEndDate(), meet.getFollowupTime());
 		return dbInstance.getCurrentEntityManager().merge(meet);
 	}
 	
+	private void updateDates(AdobeConnectMeetingImpl meet, Date start, long leadTime, Date end, long followupTime) {
+		Calendar cal = Calendar.getInstance();
+		if(start == null) {
+			meet.setStartDate(null);
+			meet.setLeadTime(0);
+			meet.setStartWithLeadTime(null);
+		} else {
+			start = cleanDate(start);
+			if(leadTime > 0) {
+				cal.add(Calendar.MINUTE, -(int)leadTime);
+			}
+			meet.setStartDate(start);
+			meet.setLeadTime(leadTime);
+			meet.setStartWithLeadTime(cal.getTime());
+		}
+		
+		if(end == null) {
+			meet.setEndDate(null);
+			meet.setFollowupTime(0);
+			meet.setEndWithFollowupTime(null);
+		} else {
+			end = cleanDate(end);
+			cal.setTime(end);
+			if(followupTime > 0) {
+				cal.add(Calendar.MINUTE, (int)followupTime);
+			}
+			meet.setEndDate(end);
+			meet.setFollowupTime(followupTime);
+			meet.setEndWithFollowupTime(cal.getTime());
+		}
+	}
+	
 	/**
 	 * Remove seconds and milliseconds
 	 * @return
@@ -107,6 +142,19 @@ public class AdobeConnectMeetingDAO {
 		return meetings == null || meetings.isEmpty() ? null : meetings.get(0);
 	}
 	
+	public List<AdobeConnectMeeting> getMeetingsBefore(Date date) {
+		StringBuilder sb = new StringBuilder(256);
+		sb.append("select meeting from adobeconnectmeeting as meeting")
+		  .append(" inner join fetch meeting.entry as v")
+		  .append(" inner join fetch v.olatResource as resource")
+		  .append(" where meeting.endWithFollowupTime is not null and meeting.endWithFollowupTime<:date");
+		
+		return dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), AdobeConnectMeeting.class)
+				.setParameter("date", date, TemporalType.TIMESTAMP)
+				.getResultList();
+	}
+	
 	public List<AdobeConnectMeeting> getMeetings(RepositoryEntryRef entry, String subIdent) {
 		StringBuilder sb = new StringBuilder(256);
 		sb.append("select meeting from adobeconnectmeeting as meeting")
@@ -126,6 +174,27 @@ public class AdobeConnectMeetingDAO {
 		return query.getResultList();
 	}
 	
+	public boolean hasMeetings(RepositoryEntryRef entry, String subIdent) {
+		StringBuilder sb = new StringBuilder(256);
+		sb.append("select meeting.key from adobeconnectmeeting as meeting")
+		  .append(" inner join meeting.entry as v")
+		  .append(" where v.key=:entryKey");
+		if(StringHelper.containsNonWhitespace(subIdent)) {
+			sb.append(" and meeting.subIdent=:subIdent");
+		}
+		
+		TypedQuery<Long> query= dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), Long.class)
+				.setParameter("entryKey", entry.getKey());
+		if(StringHelper.containsNonWhitespace(subIdent)) {
+			query.setParameter("subIdent", subIdent);
+		}
+		List<Long> keys = query.setFirstResult(0)
+				.setMaxResults(1)
+				.getResultList();
+		return keys != null && !keys.isEmpty() && keys.get(0) != null && keys.get(0).longValue() >= 0;
+	}
+	
 	public List<AdobeConnectMeeting> getMeetings(BusinessGroupRef businessGroup) {
 		StringBuilder sb = new StringBuilder(256);
 		sb.append("select meeting from adobeconnectmeeting as meeting")
@@ -138,6 +207,18 @@ public class AdobeConnectMeetingDAO {
 				.getResultList();
 	}
 	
+	public boolean hasMeetings(BusinessGroupRef businessGroup) {
+		StringBuilder sb = new StringBuilder(256);
+		sb.append("select meeting.key from adobeconnectmeeting as meeting")
+		  .append(" where meeting.businessGroup.key=:groupKey");
+		
+		List<Long> keys = dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), Long.class)
+				.setParameter("groupKey", businessGroup.getKey())
+				.getResultList();
+		return keys != null && !keys.isEmpty() && keys.get(0) != null && keys.get(0).longValue() >= 0;
+	}
+	
 	public List<AdobeConnectMeeting> getAllMeetings() {
 		StringBuilder sb = new StringBuilder(256);
 		sb.append("select meeting from adobeconnectmeeting as meeting")
diff --git a/src/main/java/org/olat/modules/adobeconnect/manager/AdobeConnectSPI.java b/src/main/java/org/olat/modules/adobeconnect/manager/AdobeConnectSPI.java
index 01a28250506..e90c74cb80c 100644
--- a/src/main/java/org/olat/modules/adobeconnect/manager/AdobeConnectSPI.java
+++ b/src/main/java/org/olat/modules/adobeconnect/manager/AdobeConnectSPI.java
@@ -66,6 +66,8 @@ public interface AdobeConnectSPI {
 	 */
 	public List<AdobeConnectSco> getFolderByName(String name, AdobeConnectErrors errors);
 	
+	public List<AdobeConnectSco> getMeetingByName(String name, AdobeConnectErrors errors);
+	
 	public List<AdobeConnectSco> getTemplates();
 	
 	public List<AdobeConnectSco> getRecordings(AdobeConnectMeeting meeting, AdobeConnectErrors error);
diff --git a/src/main/java/org/olat/modules/adobeconnect/manager/AdobeConnectUtils.java b/src/main/java/org/olat/modules/adobeconnect/manager/AdobeConnectUtils.java
index 02494744a98..86ff21c7baa 100644
--- a/src/main/java/org/olat/modules/adobeconnect/manager/AdobeConnectUtils.java
+++ b/src/main/java/org/olat/modules/adobeconnect/manager/AdobeConnectUtils.java
@@ -74,7 +74,6 @@ public class AdobeConnectUtils {
 			HttpEntity entity = response.getEntity();
 			Document doc = getDocumentFromEntity(entity);
 			if(isStatusOk(doc)) {
-				print(doc);
 				Header header = response.getFirstHeader("Set-Cookie");
 				if(header != null) {
 					session = BreezeSession.valueOf(header);
@@ -86,6 +85,8 @@ public class AdobeConnectUtils {
 						session = infoSession;
 					}
 				}
+			} else {
+				print(doc);
 			}
 		} catch (Exception e) {
 			log.error("", e);
@@ -166,6 +167,15 @@ public class AdobeConnectUtils {
 		return true;
 	}
 	
+	protected static final boolean isStatusNoData(Document doc) {
+		NodeList permissionList = doc.getElementsByTagName("status");
+		if(permissionList != null && permissionList.getLength() == 1) {
+			Element status = (Element)permissionList.item(0);
+			return "no-data".equalsIgnoreCase(status.getAttribute("code"));
+		}
+		return true;
+	}
+	
 	protected static final void error(Document doc, AdobeConnectErrors errors) {
 		NodeList permissionList = doc.getElementsByTagName("status");
 		if(permissionList != null && permissionList.getLength() == 1) {
diff --git a/src/main/java/org/olat/modules/adobeconnect/manager/DFNprovider.java b/src/main/java/org/olat/modules/adobeconnect/manager/DFNprovider.java
index 5f649a7d42e..21d9f3ceae6 100644
--- a/src/main/java/org/olat/modules/adobeconnect/manager/DFNprovider.java
+++ b/src/main/java/org/olat/modules/adobeconnect/manager/DFNprovider.java
@@ -51,9 +51,11 @@ public class DFNprovider extends AbstractAdobeConnectProvider {
 	
 	private static final Logger log = Tracing.createLoggerFor(DFNprovider.class);
 	
+	public static final String DFN_ID = "dfn";
+	
 	@Override
 	public String getId() {
-		return "dfn";
+		return DFN_ID;
 	}
 
 	@Override
diff --git a/src/main/java/org/olat/modules/adobeconnect/manager/NoAdapterProvider.java b/src/main/java/org/olat/modules/adobeconnect/manager/NoAdapterProvider.java
index 240b8a22a7d..3757e27ad18 100644
--- a/src/main/java/org/olat/modules/adobeconnect/manager/NoAdapterProvider.java
+++ b/src/main/java/org/olat/modules/adobeconnect/manager/NoAdapterProvider.java
@@ -91,6 +91,12 @@ public class NoAdapterProvider implements AdobeConnectSPI {
 		return new ArrayList<>();
 	}
 
+	@Override
+	public List<AdobeConnectSco> getMeetingByName(String name, AdobeConnectErrors errors) {
+		errors.append(new AdobeConnectError(AdobeConnectErrorCodes.serverNotAvailable));
+		return new ArrayList<>();
+	}
+
 	@Override
 	public List<AdobeConnectSco> getTemplates() {
 		return new ArrayList<>();
diff --git a/src/main/java/org/olat/modules/adobeconnect/model/AdobeConnectErrors.java b/src/main/java/org/olat/modules/adobeconnect/model/AdobeConnectErrors.java
index c43b9d7cc7e..379020bb2c2 100644
--- a/src/main/java/org/olat/modules/adobeconnect/model/AdobeConnectErrors.java
+++ b/src/main/java/org/olat/modules/adobeconnect/model/AdobeConnectErrors.java
@@ -51,4 +51,13 @@ public class AdobeConnectErrors implements Serializable {
 	public boolean hasErrors() {
 		return !errors.isEmpty();
 	}
+	
+	public String getErrorMessages() {
+		StringBuilder sb = new StringBuilder(256);
+		for(AdobeConnectError error:errors) {
+			if(sb.length() > 0) sb.append(", ");
+			sb.append(error.getCode() == null ? "UNKOWN" : error.getCode().name());
+		}
+		return sb.toString();
+	}
 }
diff --git a/src/main/java/org/olat/modules/adobeconnect/model/AdobeConnectMeetingImpl.java b/src/main/java/org/olat/modules/adobeconnect/model/AdobeConnectMeetingImpl.java
index 2f6a12ff36e..3107824d5fb 100644
--- a/src/main/java/org/olat/modules/adobeconnect/model/AdobeConnectMeetingImpl.java
+++ b/src/main/java/org/olat/modules/adobeconnect/model/AdobeConnectMeetingImpl.java
@@ -72,14 +72,33 @@ public class AdobeConnectMeetingImpl implements Persistable, AdobeConnectMeeting
 	private String description;
 	@Column(name="a_start_date", nullable=true, insertable=true, updatable=true)
 	private Date startDate;
+	@Column(name="a_leadtime", nullable=true, insertable=true, updatable=true)
+	private long leadTime;
+	@Temporal(TemporalType.TIMESTAMP)
+	@Column(name="a_start_with_leadtime", nullable=true, insertable=true, updatable=true)
+	private Date startWithLeadTime;
+	
 	@Column(name="a_end_date", nullable=true, insertable=true, updatable=true)
 	private Date endDate;
+	@Column(name="a_followuptime", nullable=true, insertable=true, updatable=true)
+	private long followupTime;
+	@Temporal(TemporalType.TIMESTAMP)
+	@Column(name="a_end_with_followuptime", nullable=true, insertable=true, updatable=true)
+	private Date endWithFollowupTime;
 	
-	@Column(name="a_sco_id", nullable=true, insertable=true, updatable=false)
+	@Column(name="a_permanent", nullable=false, insertable=true, updatable=true)
+	private boolean permanent;
+	
+	@Column(name="a_opened", nullable=false, insertable=true, updatable=true)
+	private boolean opened;
+	
+	@Column(name="a_template_id", nullable=true, insertable=true, updatable=true)
+	private String templateId;
+	@Column(name="a_sco_id", nullable=true, insertable=true, updatable=true)
 	private String scoId;
-	@Column(name="a_folder_id", nullable=true, insertable=true, updatable=false)
+	@Column(name="a_folder_id", nullable=true, insertable=true, updatable=true)
 	private String folderId;
-	@Column(name="a_env_name", nullable=true, insertable=true, updatable=false)
+	@Column(name="a_env_name", nullable=true, insertable=true, updatable=true)
 	private String envName;
 	
 	@Column(name="a_shared_documents", nullable=true, insertable=true, updatable=true)
@@ -142,6 +161,16 @@ public class AdobeConnectMeetingImpl implements Persistable, AdobeConnectMeeting
 		this.folderId = folderId;
 	}
 
+	@Override
+	public String getTemplateId() {
+		return templateId;
+	}
+
+	@Override
+	public void setTemplateId(String templateId) {
+		this.templateId = templateId;
+	}
+
 	@Override
 	public String getName() {
 		return name;
@@ -162,6 +191,16 @@ public class AdobeConnectMeetingImpl implements Persistable, AdobeConnectMeeting
 		this.description = description;
 	}
 
+	@Override
+	public boolean isPermanent() {
+		return permanent;
+	}
+
+	@Override
+	public void setPermanent(boolean permanent) {
+		this.permanent = permanent;
+	}
+
 	@Override
 	public Date getStartDate() {
 		return startDate;
@@ -172,6 +211,25 @@ public class AdobeConnectMeetingImpl implements Persistable, AdobeConnectMeeting
 		this.startDate = start;
 	}
 
+	@Override
+	public long getLeadTime() {
+		return leadTime;
+	}
+
+	@Override
+	public void setLeadTime(long leadTime) {
+		this.leadTime = leadTime;
+	}
+
+	@Override
+	public Date getStartWithLeadTime() {
+		return startWithLeadTime;
+	}
+
+	public void setStartWithLeadTime(Date startWithLeadTime) {
+		this.startWithLeadTime = startWithLeadTime;
+	}
+
 	@Override
 	public Date getEndDate() {
 		return endDate;
@@ -182,6 +240,25 @@ public class AdobeConnectMeetingImpl implements Persistable, AdobeConnectMeeting
 		this.endDate = end;
 	}
 
+	@Override
+	public long getFollowupTime() {
+		return followupTime;
+	}
+
+	@Override
+	public void setFollowupTime(long followupTime) {
+		this.followupTime = followupTime;
+	}
+
+	@Override
+	public Date getEndWithFollowupTime() {
+		return endWithFollowupTime;
+	}
+
+	public void setEndWithFollowupTime(Date endWithFollowupTime) {
+		this.endWithFollowupTime = endWithFollowupTime;
+	}
+
 	@Override
 	public String getEnvName() {
 		return envName;
@@ -191,6 +268,16 @@ public class AdobeConnectMeetingImpl implements Persistable, AdobeConnectMeeting
 		this.envName = envName;
 	}
 
+	@Override
+	public boolean isOpened() {
+		return opened;
+	}
+
+	@Override
+	public void setOpened(boolean opened) {
+		this.opened = opened;
+	}
+
 	public String getSharedDocuments() {
 		return sharedDocuments;
 	}
diff --git a/src/main/java/org/olat/modules/adobeconnect/model/AdobeConnectSco.java b/src/main/java/org/olat/modules/adobeconnect/model/AdobeConnectSco.java
index ef890ee013d..73bf4d92440 100644
--- a/src/main/java/org/olat/modules/adobeconnect/model/AdobeConnectSco.java
+++ b/src/main/java/org/olat/modules/adobeconnect/model/AdobeConnectSco.java
@@ -30,6 +30,7 @@ import java.util.Date;
 public class AdobeConnectSco {
 	
 	private String scoId;
+	private String folderId;
 	private String type;
 	private String urlPath;
 	private String name;
@@ -47,6 +48,14 @@ public class AdobeConnectSco {
 		this.scoId = scoId;
 	}
 
+	public String getFolderId() {
+		return folderId;
+	}
+
+	public void setFolderId(String folderId) {
+		this.folderId = folderId;
+	}
+
 	public String getType() {
 		return type;
 	}
diff --git a/src/main/java/org/olat/modules/adobeconnect/ui/AdobeConnectAdminMeetingsController.java b/src/main/java/org/olat/modules/adobeconnect/ui/AdobeConnectAdminMeetingsController.java
index ad8e179cdd1..28f1c100751 100644
--- a/src/main/java/org/olat/modules/adobeconnect/ui/AdobeConnectAdminMeetingsController.java
+++ b/src/main/java/org/olat/modules/adobeconnect/ui/AdobeConnectAdminMeetingsController.java
@@ -45,6 +45,7 @@ import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory;
 import org.olat.core.util.StringHelper;
 import org.olat.modules.adobeconnect.AdobeConnectManager;
 import org.olat.modules.adobeconnect.AdobeConnectMeeting;
+import org.olat.modules.adobeconnect.AdobeConnectModule;
 import org.olat.modules.adobeconnect.model.AdobeConnectErrors;
 import org.olat.modules.adobeconnect.ui.AdobeConnectMeetingTableModel.ACMeetingsCols;
 import org.olat.modules.gotomeeting.ui.GoToMeetingTableModel.MeetingsCols;
@@ -63,6 +64,8 @@ public class AdobeConnectAdminMeetingsController extends FormBasicController {
 
 	private DialogBoxController confirmDelete;
 	
+	@Autowired
+	private AdobeConnectModule adobeConnectModule;
 	@Autowired
 	private AdobeConnectManager adobeConnectManager;
 	
@@ -76,6 +79,9 @@ public class AdobeConnectAdminMeetingsController extends FormBasicController {
 	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
 		FlexiTableColumnModel columnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel();
 		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(ACMeetingsCols.name));
+		if(!adobeConnectModule.isSingleMeetingMode()) {
+			columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(ACMeetingsCols.permanent));
+		}
 		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(ACMeetingsCols.start));
 		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(ACMeetingsCols.end));
 		FlexiCellRenderer renderer = new StaticFlexiCellRenderer("resource", new TextFlexiCellRenderer());
diff --git a/src/main/java/org/olat/modules/adobeconnect/ui/AdobeConnectConfigurationController.java b/src/main/java/org/olat/modules/adobeconnect/ui/AdobeConnectConfigurationController.java
index 4fa2ce0035b..7715865eafd 100644
--- a/src/main/java/org/olat/modules/adobeconnect/ui/AdobeConnectConfigurationController.java
+++ b/src/main/java/org/olat/modules/adobeconnect/ui/AdobeConnectConfigurationController.java
@@ -21,6 +21,7 @@ package org.olat.modules.adobeconnect.ui;
 
 import java.net.URI;
 import java.net.URISyntaxException;
+import java.util.Arrays;
 import java.util.List;
 
 import org.olat.collaboration.CollaborationToolsFactory;
@@ -54,14 +55,21 @@ import org.springframework.beans.factory.annotation.Autowired;
  */
 public class AdobeConnectConfigurationController extends FormBasicController {
 	
+	private static final String[] CLEAN_KEYS = { "-", "1", "2", "3", "4", "5", "7", "14", "21", "30" };
+	private static final String[] CREATE_KEYS = { "immediately", "differed" };
+	private static final String[] SINGLE_KEYS = { "single", "perdate" };
 	private static final String PLACEHOLDER = "xxx-placeholder-xxx";
 	
 	private FormLink checkLink;
 	private TextElement urlEl;
 	private TextElement loginEl;
 	private TextElement passwordEl;
+	private TextElement accountIdEl;
 	private SpacerElement spacerEl;
 	private SingleSelection providerEl;
+	private SingleSelection cleanMeetingsEl;
+	private SingleSelection createMeetingEl;
+	private SingleSelection singleMeetingEl;
 	private MultipleSelectionElement moduleEnabled;
 
 	private static final String[] enabledKeys = new String[]{"on"};
@@ -129,6 +137,43 @@ public class AdobeConnectConfigurationController extends FormBasicController {
 		passwordEl = uifactory.addPasswordElement("aconnect-password", "option.adminpassword", 32, credential, formLayout);
 		passwordEl.setAutocomplete("new-password");
 		
+		String accountId = adobeConnectModule.getAccountId();
+		accountIdEl = uifactory.addTextElement("aconnect-id", "option.accountid", 32, accountId, formLayout);
+		accountIdEl.setHelpTextKey("option.accountid.explain", null);
+		
+		// delete meeting
+		String[] cleanValues = Arrays.copyOf(CLEAN_KEYS, CLEAN_KEYS.length);
+		cleanValues[0] = translate("option.dont.clean.meetings");
+		cleanMeetingsEl = uifactory.addDropdownSingleselect("option.clean.meetings", formLayout, CLEAN_KEYS, cleanValues);
+		if(adobeConnectModule.isCleanupMeetings()) {
+			long days = adobeConnectModule.getDaysToKeep();
+			String dayStr = Long.toString(days);
+			for(String key:CLEAN_KEYS) {
+				if(dayStr.equals(key)) {
+					cleanMeetingsEl.select(key, true);
+				}
+			}
+		} else {
+			cleanMeetingsEl.select(CLEAN_KEYS[0], true);
+		}
+		
+		// create meeting ASAP
+		String[] createValues = new String[] { translate("option.create.meeting.immediately"), translate("option.create.meeting.differed") };
+		createMeetingEl = uifactory.addRadiosHorizontal("option.create.meeting", "option.create.meeting", formLayout, CREATE_KEYS, createValues);
+		if(adobeConnectModule.isCreateMeetingImmediately()) {
+			createMeetingEl.select(CREATE_KEYS[0], true);
+		} else {
+			createMeetingEl.select(CREATE_KEYS[1], true);
+		}
+		
+		String[] singleMeetingValues = new String[] { translate("option.single.meeting.single"), translate("option.single.meeting.perdate") };
+		singleMeetingEl = uifactory.addRadiosHorizontal("option.single.meeting", formLayout, SINGLE_KEYS, singleMeetingValues);
+		if(adobeConnectModule.isSingleMeetingMode()) {
+			singleMeetingEl.select(SINGLE_KEYS[0], true);
+		} else {
+			singleMeetingEl.select(SINGLE_KEYS[1], true);
+		}
+		
 		//buttons save - check
 		FormLayoutContainer buttonLayout = FormLayoutContainer.createButtonLayout("save", getTranslator());
 		formLayout.add(buttonLayout);
@@ -247,6 +292,16 @@ public class AdobeConnectConfigurationController extends FormBasicController {
 				adobeConnectModule.setAdobeConnectURI(new URI(url));
 				adobeConnectModule.setAdminLogin(loginEl.getValue());
 				adobeConnectModule.setProviderId(providerEl.getSelectedKey());
+				adobeConnectModule.setAccountId(accountIdEl.getValue());
+				if(cleanMeetingsEl.isSelected(0)) {
+					adobeConnectModule.setCleanupMeetings(false);
+					adobeConnectModule.setDaysToKeep(null);
+				} else {
+					adobeConnectModule.setCleanupMeetings(true);
+					adobeConnectModule.setDaysToKeep(cleanMeetingsEl.getSelectedKey());
+				}
+				adobeConnectModule.setSingleMeetingMode(singleMeetingEl.isSelected(0));
+				adobeConnectModule.setCreateMeetingImmediately(createMeetingEl.isSelected(0));
 				String credential = passwordEl.getValue();
 				if(!PLACEHOLDER.equals(credential)) {
 					adobeConnectModule.setAdminPassword(credential);
@@ -258,6 +313,7 @@ public class AdobeConnectConfigurationController extends FormBasicController {
 				adobeConnectModule.setAdobeConnectURI(null);
 				adobeConnectModule.setAdminLogin(null);
 				adobeConnectModule.setAdminPassword(null);
+				adobeConnectModule.setAccountId(null);
 			}
 			CollaborationToolsFactory.getInstance().initAvailableTools();
 		} catch (URISyntaxException e) {
diff --git a/src/main/java/org/olat/modules/adobeconnect/ui/AdobeConnectEditMeetingsController.java b/src/main/java/org/olat/modules/adobeconnect/ui/AdobeConnectEditMeetingsController.java
index 929491e6f31..593ac41fd02 100644
--- a/src/main/java/org/olat/modules/adobeconnect/ui/AdobeConnectEditMeetingsController.java
+++ b/src/main/java/org/olat/modules/adobeconnect/ui/AdobeConnectEditMeetingsController.java
@@ -44,6 +44,7 @@ import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory;
 import org.olat.group.BusinessGroup;
 import org.olat.modules.adobeconnect.AdobeConnectManager;
 import org.olat.modules.adobeconnect.AdobeConnectMeeting;
+import org.olat.modules.adobeconnect.AdobeConnectModule;
 import org.olat.modules.adobeconnect.model.AdobeConnectErrors;
 import org.olat.modules.adobeconnect.ui.AdobeConnectMeetingTableModel.ACMeetingsCols;
 import org.olat.modules.gotomeeting.ui.GoToMeetingTableModel.MeetingsCols;
@@ -72,6 +73,8 @@ public class AdobeConnectEditMeetingsController extends FormBasicController {
 	private final BusinessGroup businessGroup;
 	private final AdobeConnectMeetingDefaultConfiguration configuration;
 	
+	@Autowired
+	private AdobeConnectModule adobeConnectModule;
 	@Autowired
 	private AdobeConnectManager adobeConnectManager;
 	
@@ -97,6 +100,9 @@ public class AdobeConnectEditMeetingsController extends FormBasicController {
 		//add the table
 		FlexiTableColumnModel columnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel();
 		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(ACMeetingsCols.name));
+		if(!adobeConnectModule.isSingleMeetingMode()) {
+			columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(ACMeetingsCols.permanent));
+		}
 		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(ACMeetingsCols.start));
 		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(ACMeetingsCols.end));
 		if(!readOnly) {
diff --git a/src/main/java/org/olat/modules/adobeconnect/ui/AdobeConnectEvent.java b/src/main/java/org/olat/modules/adobeconnect/ui/AdobeConnectEvent.java
new file mode 100644
index 00000000000..e18887e4b80
--- /dev/null
+++ b/src/main/java/org/olat/modules/adobeconnect/ui/AdobeConnectEvent.java
@@ -0,0 +1,53 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.adobeconnect.ui;
+
+import org.olat.core.util.event.MultiUserEvent;
+
+/**
+ * 
+ * Initial date: 4 juil. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class AdobeConnectEvent extends MultiUserEvent {
+
+	private static final long serialVersionUID = 3199767160246830180L;
+
+	public static final String OPEN_MEETING = "adobe-connect-open-meeting";
+	public static final String CREATE_MEETING = "adobe-connect-create-meeting";
+	
+	private final Long meetingKey;
+	private final Long actingIdentityKey;
+	
+	public AdobeConnectEvent(String name, Long meetingKey, Long actingIdentityKey) {
+		super(name);
+		this.meetingKey = meetingKey;
+		this.actingIdentityKey = actingIdentityKey;
+	}
+
+	public Long getMeetingKey() {
+		return meetingKey;
+	}
+
+	public Long getActingIdentityKey() {
+		return actingIdentityKey;
+	}
+}
diff --git a/src/main/java/org/olat/modules/adobeconnect/ui/AdobeConnectMeetingController.java b/src/main/java/org/olat/modules/adobeconnect/ui/AdobeConnectMeetingController.java
index 58f5c118179..2bcc7a37702 100644
--- a/src/main/java/org/olat/modules/adobeconnect/ui/AdobeConnectMeetingController.java
+++ b/src/main/java/org/olat/modules/adobeconnect/ui/AdobeConnectMeetingController.java
@@ -45,8 +45,12 @@ import org.olat.core.gui.control.WindowControl;
 import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController;
 import org.olat.core.gui.media.MediaResource;
 import org.olat.core.gui.media.RedirectMediaResource;
+import org.olat.core.id.OLATResourceable;
 import org.olat.core.util.Formatter;
 import org.olat.core.util.StringHelper;
+import org.olat.core.util.coordinate.CoordinatorManager;
+import org.olat.core.util.event.GenericEventListener;
+import org.olat.core.util.resource.OresHelper;
 import org.olat.modules.adobeconnect.AdobeConnectManager;
 import org.olat.modules.adobeconnect.AdobeConnectMeeting;
 import org.olat.modules.adobeconnect.AdobeConnectMeetingPermission;
@@ -62,7 +66,7 @@ import org.springframework.beans.factory.annotation.Autowired;
  * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
  *
  */
-public class AdobeConnectMeetingController extends FormBasicController {
+public class AdobeConnectMeetingController extends FormBasicController implements GenericEventListener {
 	
 	private final boolean readOnly;
 	private final boolean moderator;
@@ -70,11 +74,18 @@ public class AdobeConnectMeetingController extends FormBasicController {
 	private AdobeConnectMeeting meeting;
 	
 	private boolean registered;
-	private final boolean validMeeting;
+	private boolean meetingCreated;
+	private boolean validMeeting;
+	private final boolean guestsAccess;
+	private final boolean userMeetingsDates;
+	private final boolean moderatorStartMeeting;
+	private final OLATResourceable meetingOres;
 
 	private int counter;
 	private Link joinButton;
+	
 	private FormLink registerButton;
+	private FormLink createMeetingButton;
 	private FormLink sharedDocumentButton;
 	private FlexiTableElement contentTableEl;
 	private AdobeConnectContentTableModel contentModel;
@@ -88,17 +99,25 @@ public class AdobeConnectMeetingController extends FormBasicController {
 	private AdobeConnectManager adobeConnectManager;
 	
 	public AdobeConnectMeetingController(UserRequest ureq, WindowControl wControl,
-			AdobeConnectMeeting meeting, boolean administrator, boolean moderator, boolean readOnly) {
+			AdobeConnectMeeting meeting, AdobeConnectMeetingDefaultConfiguration configuration,
+			boolean administrator, boolean moderator, boolean readOnly) {
 		super(ureq, wControl, "meeting");
 		this.meeting = meeting;
 		this.readOnly = readOnly;
 		this.moderator = moderator;
 		this.administrator = administrator;
+		meetingOres = OresHelper.createOLATResourceableInstance(AdobeConnectMeeting.class.getSimpleName(), meeting.getKey());
+		CoordinatorManager.getInstance().getCoordinator().getEventBus().registerFor(this, getIdentity(), meetingOres);
+		
+		meetingCreated = StringHelper.containsNonWhitespace(meeting.getScoId());
 		validMeeting = adobeConnectModule.getBaseUrl().equals(meeting.getEnvName());
+		userMeetingsDates = configuration.isUseMeetingDates();
+		moderatorStartMeeting = configuration.isModeratorStartMeeting();
+		guestsAccess = configuration.isAllowGuestAccess();
 		
 		initForm(ureq);
 		
-		if(validMeeting) {
+		if(validMeeting && meetingCreated) {
 			AdobeConnectErrors errors = new AdobeConnectErrors();
 			registered = adobeConnectManager.isRegistered(meeting, getIdentity(), getPermission(), errors);
 			loadModel();
@@ -106,7 +125,7 @@ public class AdobeConnectMeetingController extends FormBasicController {
 				getWindowControl().setWarning(AdobeConnectErrorHelper.formatErrors(getTranslator(), errors));
 			}
 		}
-		updateButtons();
+		updateButtonsAndStatus();
 	}
 
 	@Override
@@ -125,18 +144,17 @@ public class AdobeConnectMeetingController extends FormBasicController {
 			if(meeting.getEndDate() != null) {
 				String end = Formatter.getInstance(getLocale()).formatDateAndTime(meeting.getEndDate());
 				layoutCont.contextPut("end", end);
-				if(ended) {
-					layoutCont.contextPut("ended", Boolean.TRUE);
-				}
 			}
-
-			layoutCont.contextPut("validMeeting", Boolean.valueOf(validMeeting));
 		}
 
 		registerButton = uifactory.addFormLink("meeting.register.button", flc, Link.BUTTON);
 		registerButton.setVisible(!ended);
+		
 		sharedDocumentButton = uifactory.addFormLink("meeting.share.documents", flc, Link.BUTTON);
 		sharedDocumentButton.setVisible(administrator || moderator);
+		
+		createMeetingButton = uifactory.addFormLink("meeting.create.button", flc, Link.BUTTON);
+		createMeetingButton.setVisible(!StringHelper.containsNonWhitespace(meeting.getScoId()));
 
 		joinButton = LinkFactory.createButtonLarge("meeting.join.button", flc.getFormItemComponent(), this);
 		joinButton.setTarget("_blank");
@@ -149,6 +167,82 @@ public class AdobeConnectMeetingController extends FormBasicController {
 		return meeting != null && meeting.getEndDate() != null && new Date().after(meeting.getEndDate());
 	}
 	
+	private boolean isValidDates() {
+		if(!userMeetingsDates) {
+			return true;
+		}
+		Date now = new Date();
+		Date start = meeting.getStartWithLeadTime();
+		Date end = meeting.getEndWithFollowupTime();
+		
+		if(start != null && start.compareTo(now) >= 0) {
+			return false;
+		}
+		if(end != null && end.compareTo(now) <= 0) {
+			return false;
+		}
+		return true;
+	}
+	
+	private void reloadButtonsAndStatus() {
+		meeting = adobeConnectManager.getMeeting(meeting);
+		updateButtonsAndStatus();
+	}
+	
+	private void updateButtonsAndStatus() {
+		boolean invalidProvider = meeting.getEnvName() != null && !meeting.getEnvName().equals(adobeConnectModule.getBaseUrl());
+		boolean meetingsExists = StringHelper.containsNonWhitespace(meeting.getScoId());
+		boolean isEnded = isEnded();
+
+		meetingCreated = StringHelper.containsNonWhitespace(meeting.getScoId());
+		validMeeting = adobeConnectModule.getBaseUrl().equals(meeting.getEnvName());
+		
+		flc.contextPut("invalidProvider", Boolean.valueOf(invalidProvider));
+		flc.contextPut("meetingsExists", Boolean.valueOf(meetingsExists));
+		flc.contextPut("ended", Boolean.valueOf(isEnded));
+		
+		boolean accessible = !isEnded() || administrator || moderator;
+		boolean canCreate = !StringHelper.containsNonWhitespace(meeting.getScoId())
+				&& (administrator || moderator);
+		createMeetingButton.setVisible(canCreate);
+		
+		if(canCreate) {
+			registerButton.setVisible(false);
+			joinButton.setVisible(false);
+		} else if(moderator || administrator) {
+			registerButton.setVisible(false);
+			
+			joinButton.setVisible(accessible);
+			joinButton.setEnabled(!readOnly && validMeeting);
+			
+			if(!meeting.isOpened() && moderatorStartMeeting) {
+				joinButton.setCustomDisplayText(translate("meeting.start.button"));
+			} else if(isValidDates()) {
+				joinButton.setCustomDisplayText(translate("meeting.join.button"));
+			} else {
+				joinButton.setCustomDisplayText(translate("meeting.go.button"));
+			}
+		} else {
+			registerButton.setVisible(accessible && !registered && !readOnly && validMeeting);
+			boolean validDates = isValidDates();
+
+			joinButton.setVisible(accessible && registered);
+			if(!meeting.isOpened() && moderatorStartMeeting) {
+				joinButton.setEnabled(false);
+			} else {
+				joinButton.setEnabled(!readOnly && validMeeting && validDates);
+			}
+
+			if(validDates && !meeting.isOpened() && moderatorStartMeeting) {
+				flc.contextPut("notStarted", Boolean.TRUE);	
+			} else if(validDates || isEnded) {
+				flc.contextPut("notStarted", Boolean.FALSE);
+			} else {
+				flc.contextPut("notStarted", Boolean.TRUE);
+			}
+		}
+	}
+	
 	protected void initContent(FormItemContainer formLayout) {
 		FlexiTableColumnModel columnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel();
 		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(ACContentsCols.icon, new AdobeConnectIconRenderer()));
@@ -183,7 +277,7 @@ public class AdobeConnectMeetingController extends FormBasicController {
 			}
 			contentModel.setObjects(rows);
 			contentTableEl.reset(true, true, true);
-			contentTableEl.setVisible(true);
+			contentTableEl.setVisible(!rows.isEmpty() || administrator || moderator);
 			sharedDocumentButton.setVisible(!scos.isEmpty() && (administrator || moderator));
 			flc.contextPut("notRegistered", Boolean.valueOf(!registered));
 		} else {
@@ -191,17 +285,20 @@ public class AdobeConnectMeetingController extends FormBasicController {
 			sharedDocumentButton.setVisible(false);
 		}
 	}
-	
-	private void updateButtons() {
-		boolean accessible = !isEnded() || administrator || moderator;	
-		registerButton.setVisible(accessible && !registered && !readOnly && validMeeting);
-		joinButton.setVisible(accessible && registered);
-		joinButton.setEnabled(!readOnly && validMeeting);
-	}
 
 	@Override
 	protected void doDispose() {
-		//
+		CoordinatorManager.getInstance().getCoordinator().getEventBus().deregisterFor(this, meetingOres);
+	}
+
+	@Override
+	public void event(Event event) {
+		if(event instanceof AdobeConnectEvent) {
+			AdobeConnectEvent ace = (AdobeConnectEvent)event;
+			if(ace.getMeetingKey() != null && ace.getMeetingKey().equals(meeting.getKey())) {
+				reloadButtonsAndStatus();
+			}
+		}
 	}
 
 	@Override
@@ -245,18 +342,51 @@ public class AdobeConnectMeetingController extends FormBasicController {
 			doRegister();
 		} else if(sharedDocumentButton == source) {
 			doShareDocuments(ureq);
+		} else if(createMeetingButton == source) {
+			doCreateMeeting(ureq);
 		}
 		super.formInnerEvent(ureq, source, event);
 	}
 	
-	private void doJoin(UserRequest ureq) {
+	private void doCreateMeeting(UserRequest ureq) {
+		meeting = adobeConnectManager.getMeeting(meeting);
 		AdobeConnectErrors errors = new AdobeConnectErrors();
-		String meetingUrl = adobeConnectManager.join(meeting, getIdentity(), errors);
+		meeting = adobeConnectManager.createAdobeMeeting(meeting, getLocale(), guestsAccess, errors);
+		updateButtonsAndStatus();
+		
 		if(errors.hasErrors()) {
 			getWindowControl().setError(AdobeConnectErrorHelper.formatErrors(getTranslator(), errors));
 		} else {
+			AdobeConnectEvent createEvent = new AdobeConnectEvent(AdobeConnectEvent.CREATE_MEETING, meeting.getKey(), getIdentity().getKey());
+			CoordinatorManager.getInstance().getCoordinator().getEventBus().fireEventToListenersOf(createEvent, meetingOres);
+		}
+	}
+	
+	private void doJoin(UserRequest ureq) {
+		meeting = adobeConnectManager.getMeeting(meeting);
+		if(meeting == null) {
+			showWarning("warning.no.meeting");
+			fireEvent(ureq, Event.BACK_EVENT);
+			return;
+		}
+		
+		String meetingUrl = null;
+		AdobeConnectErrors errors = new AdobeConnectErrors();
+		if(meeting.isOpened() || !moderatorStartMeeting) {
+			meetingUrl = adobeConnectManager.join(meeting, getIdentity(), errors);
+		} else if(moderator || administrator) {
+			meetingUrl = adobeConnectManager.open(meeting, getIdentity(), errors);
+			AdobeConnectEvent openEvent = new AdobeConnectEvent(AdobeConnectEvent.OPEN_MEETING, meeting.getKey(), getIdentity().getKey());
+			CoordinatorManager.getInstance().getCoordinator().getEventBus().fireEventToListenersOf(openEvent, meetingOres);
+		}
+
+		if(errors.hasErrors()) {
+			getWindowControl().setError(AdobeConnectErrorHelper.formatErrors(getTranslator(), errors));
+		} else if(StringHelper.containsNonWhitespace(meetingUrl)) {
 			MediaResource redirect = new RedirectMediaResource(meetingUrl);
 			ureq.getDispatchResult().setResultingMediaResource(redirect);
+		} else {
+			showWarning("warning.no.access");
 		}
 	}
 	
@@ -265,7 +395,7 @@ public class AdobeConnectMeetingController extends FormBasicController {
 		registered = adobeConnectManager.registerFor(meeting, getIdentity(), getPermission(), errors);
 		if(registered) {
 			showInfo("meeting.successfully.registered");
-			updateButtons();
+			reloadButtonsAndStatus();
 		} else if(errors.hasErrors()) {
 			getWindowControl().setError(AdobeConnectErrorHelper.formatErrors(getTranslator(), errors));
 		} else {
diff --git a/src/main/java/org/olat/modules/adobeconnect/ui/AdobeConnectMeetingDefaultConfiguration.java b/src/main/java/org/olat/modules/adobeconnect/ui/AdobeConnectMeetingDefaultConfiguration.java
index 72ce6eb1816..4aacd6c4d82 100644
--- a/src/main/java/org/olat/modules/adobeconnect/ui/AdobeConnectMeetingDefaultConfiguration.java
+++ b/src/main/java/org/olat/modules/adobeconnect/ui/AdobeConnectMeetingDefaultConfiguration.java
@@ -27,14 +27,26 @@ package org.olat.modules.adobeconnect.ui;
  */
 public class AdobeConnectMeetingDefaultConfiguration {
 	
+	private final boolean useMeetingDates;
 	private final boolean allowGuestAccess;
+	private final boolean moderatorStartMeeting;
 	
-	public AdobeConnectMeetingDefaultConfiguration(boolean allowGuestAccess) {
+	public AdobeConnectMeetingDefaultConfiguration(boolean useMeetingDates,
+			boolean allowGuestAccess, boolean moderatorStartMeeting) {
 		this.allowGuestAccess = allowGuestAccess;
+		this.useMeetingDates = useMeetingDates;
+		this.moderatorStartMeeting = moderatorStartMeeting;
 	}
 	
 	public boolean isAllowGuestAccess() {
 		return allowGuestAccess;
 	}
 
+	public boolean isUseMeetingDates() {
+		return useMeetingDates;
+	}
+
+	public boolean isModeratorStartMeeting() {
+		return moderatorStartMeeting;
+	}
 }
diff --git a/src/main/java/org/olat/modules/adobeconnect/ui/AdobeConnectMeetingTableModel.java b/src/main/java/org/olat/modules/adobeconnect/ui/AdobeConnectMeetingTableModel.java
index 5d9d53efc6b..e0857a6820d 100644
--- a/src/main/java/org/olat/modules/adobeconnect/ui/AdobeConnectMeetingTableModel.java
+++ b/src/main/java/org/olat/modules/adobeconnect/ui/AdobeConnectMeetingTableModel.java
@@ -64,6 +64,7 @@ implements SortableFlexiTableDataModel<AdobeConnectMeeting> {
 	public Object getValueAt(AdobeConnectMeeting row, int col) {
 		switch(ACMeetingsCols.values()[col]) {
 			case name: return row.getName();
+			case permanent: return row.isPermanent();
 			case start: return row.getStartDate();
 			case end: return row.getEndDate();
 			case resource: return getResourceName(row);
@@ -89,6 +90,7 @@ implements SortableFlexiTableDataModel<AdobeConnectMeeting> {
 	public enum ACMeetingsCols implements FlexiSortableColumnDef {
 		
 		name("meeting.name"),
+		permanent("table.header.permanent"),
 		start("meeting.start"),
 		end("meeting.end"),
 		resource("meeting.resource");
diff --git a/src/main/java/org/olat/modules/adobeconnect/ui/AdobeConnectMeetingsController.java b/src/main/java/org/olat/modules/adobeconnect/ui/AdobeConnectMeetingsController.java
index 09fed45ec1f..81580908dc5 100644
--- a/src/main/java/org/olat/modules/adobeconnect/ui/AdobeConnectMeetingsController.java
+++ b/src/main/java/org/olat/modules/adobeconnect/ui/AdobeConnectMeetingsController.java
@@ -41,6 +41,7 @@ import org.olat.core.gui.control.WindowControl;
 import org.olat.group.BusinessGroup;
 import org.olat.modules.adobeconnect.AdobeConnectManager;
 import org.olat.modules.adobeconnect.AdobeConnectMeeting;
+import org.olat.modules.adobeconnect.AdobeConnectModule;
 import org.olat.modules.adobeconnect.ui.AdobeConnectMeetingTableModel.ACMeetingsCols;
 import org.olat.repository.RepositoryEntry;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -61,16 +62,20 @@ public class AdobeConnectMeetingsController extends FormBasicController {
 	private final RepositoryEntry entry;
 	private final String subIdent;
 	private final BusinessGroup businessGroup;
+	private final boolean showPermanentCol;
 	
+	@Autowired
+	private AdobeConnectModule adobeConnectModule;
 	@Autowired
 	private AdobeConnectManager adobeConnectManager;
 	
 	public AdobeConnectMeetingsController(UserRequest ureq, WindowControl wControl, RepositoryEntry entry, String subIdent,
-			BusinessGroup businessGroup) {
+			BusinessGroup businessGroup, boolean administrator, boolean moderator) {
 		super(ureq, wControl, "meetings");
 		this.entry = entry;
 		this.subIdent = subIdent;
 		this.businessGroup = businessGroup;
+		showPermanentCol = (administrator || moderator) && !adobeConnectModule.isSingleMeetingMode();
 		
 		initForm(ureq);
 		updateModel();
@@ -81,6 +86,9 @@ public class AdobeConnectMeetingsController extends FormBasicController {
 		// upcoming meetings table
 		FlexiTableColumnModel columnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel();
 		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(ACMeetingsCols.name));
+		if(showPermanentCol) {
+			columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(ACMeetingsCols.permanent));
+		}
 		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(ACMeetingsCols.start));
 		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(ACMeetingsCols.end));
 		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel("select", translate("select"), "select"));
@@ -97,6 +105,9 @@ public class AdobeConnectMeetingsController extends FormBasicController {
 		// past meetings
 		FlexiTableColumnModel pastColumnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel();
 		pastColumnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(ACMeetingsCols.name));
+		if(showPermanentCol) {
+			pastColumnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(ACMeetingsCols.permanent));
+		}
 		pastColumnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(ACMeetingsCols.start));
 		pastColumnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(ACMeetingsCols.end));
 		pastColumnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel("select", translate("select"), "select"));
diff --git a/src/main/java/org/olat/modules/adobeconnect/ui/AdobeConnectRunController.java b/src/main/java/org/olat/modules/adobeconnect/ui/AdobeConnectRunController.java
index 75f72407de9..302f6feb3bb 100644
--- a/src/main/java/org/olat/modules/adobeconnect/ui/AdobeConnectRunController.java
+++ b/src/main/java/org/olat/modules/adobeconnect/ui/AdobeConnectRunController.java
@@ -101,7 +101,7 @@ public class AdobeConnectRunController extends BasicController implements Activa
 			doOpenMeetings(ureq);
 		} else {
 			mainVC = createVelocityContainer("run");
-			meetingsCtrl = new AdobeConnectMeetingsController(ureq, wControl, entry, subIdent, group);
+			meetingsCtrl = new AdobeConnectMeetingsController(ureq, wControl, entry, subIdent, group, admin, moderator);
 			listenTo(meetingsCtrl);
 			mainVC.put("meetings", meetingsCtrl.getInitialComponent());
 		}
@@ -123,14 +123,23 @@ public class AdobeConnectRunController extends BasicController implements Activa
 		if("Meetings".equalsIgnoreCase(type)) {
 			if(canView) {
 				doOpenMeetings(ureq);
+				if(segmentView != null) {
+					segmentView.select(meetingsLink);
+				}
 			}
 		} else if("Administration".equalsIgnoreCase(type)) {
 			if(administrator) {
 				doOpenAdmin(ureq);
+				if(segmentView != null) { 
+					segmentView.select(adminLink);
+				}
 			}
 		} else if("Meeting".equalsIgnoreCase(type)) {
 			if(canView) {
 				doOpenMeetings(ureq);
+				if(segmentView != null) {
+					segmentView.select(meetingsLink);
+				}
 				Long meetingKey = entries.get(0).getOLATResourceable().getResourceableId();
 				if(meetingsCtrl.hasMeetingByKey(meetingKey)) {
 					doSelectMeeting(ureq, meetingsCtrl.getMeetingByKey(meetingKey));
@@ -142,9 +151,7 @@ public class AdobeConnectRunController extends BasicController implements Activa
 	@Override
 	protected void event(UserRequest ureq, Component source, Event event) {
 		if(backLink == source) {
-			mainVC.remove(meetingCtrl.getInitialComponent());
-			removeAsListenerAndDispose(meetingCtrl);
-			meetingCtrl = null;
+			back();
 		} else if(source == segmentView) {
 			if(event instanceof SegmentViewEvent) {
 				SegmentViewEvent sve = (SegmentViewEvent)event;
@@ -166,14 +173,24 @@ public class AdobeConnectRunController extends BasicController implements Activa
 				SelectAdobeConnectMeetingEvent se = (SelectAdobeConnectMeetingEvent)event;
 				doSelectMeeting(ureq, se.getMeeting());
 			}
+		} else if(meetingCtrl == source) {
+			if(event == Event.BACK_EVENT) {
+				back();
+			}
 		}
 	}
 	
+	private void back() {
+		mainVC.remove(meetingCtrl.getInitialComponent());
+		removeAsListenerAndDispose(meetingCtrl);
+		meetingCtrl = null;
+	}
+	
 	private void doOpenMeetings(UserRequest ureq) {
 		if(meetingsCtrl == null) {
 			WindowControl bwControl = addToHistory(ureq, OresHelper.createOLATResourceableInstance("Meetings", 0l), null);
 			meetingsCtrl = new AdobeConnectMeetingsController(ureq, bwControl,
-					entry, subIdent, group);
+					entry, subIdent, group, administrator, moderator);
 			listenTo(meetingsCtrl);
 		} else {
 			meetingsCtrl.updateModel();
@@ -196,10 +213,15 @@ public class AdobeConnectRunController extends BasicController implements Activa
 	
 	private void doSelectMeeting(UserRequest ureq, AdobeConnectMeeting meeting) {
 		removeAsListenerAndDispose(meetingCtrl);
-
-		WindowControl bwControl = addToHistory(ureq, OresHelper.createOLATResourceableInstance("Meeting", meeting.getKey()), null);
-		meetingCtrl = new AdobeConnectMeetingController(ureq, bwControl, meeting, administrator, moderator, readOnly);
-		listenTo(meetingCtrl);
-		mainVC.put("meeting", meetingCtrl.getInitialComponent());
+		meetingCtrl = null;
+		
+		if(meeting == null) {
+			showWarning("warning.no.meeting");
+		} else {
+			WindowControl bwControl = addToHistory(ureq, OresHelper.createOLATResourceableInstance("Meeting", meeting.getKey()), null);
+			meetingCtrl = new AdobeConnectMeetingController(ureq, bwControl, meeting, configuration, administrator, moderator, readOnly);
+			listenTo(meetingCtrl);
+			mainVC.put("meeting", meetingCtrl.getInitialComponent());
+		}
 	}
 }
diff --git a/src/main/java/org/olat/modules/adobeconnect/ui/AdobeConnectShareDocumentsController.java b/src/main/java/org/olat/modules/adobeconnect/ui/AdobeConnectShareDocumentsController.java
index 04ec13d54ac..8e11d22fa9c 100644
--- a/src/main/java/org/olat/modules/adobeconnect/ui/AdobeConnectShareDocumentsController.java
+++ b/src/main/java/org/olat/modules/adobeconnect/ui/AdobeConnectShareDocumentsController.java
@@ -1,3 +1,22 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
 package org.olat.modules.adobeconnect.ui;
 
 import java.util.ArrayList;
diff --git a/src/main/java/org/olat/modules/adobeconnect/ui/EditAdobeConnectMeetingController.java b/src/main/java/org/olat/modules/adobeconnect/ui/EditAdobeConnectMeetingController.java
index bf55f5577d9..a8e1517c333 100644
--- a/src/main/java/org/olat/modules/adobeconnect/ui/EditAdobeConnectMeetingController.java
+++ b/src/main/java/org/olat/modules/adobeconnect/ui/EditAdobeConnectMeetingController.java
@@ -39,6 +39,7 @@ import org.olat.core.util.StringHelper;
 import org.olat.group.BusinessGroup;
 import org.olat.modules.adobeconnect.AdobeConnectManager;
 import org.olat.modules.adobeconnect.AdobeConnectMeeting;
+import org.olat.modules.adobeconnect.AdobeConnectModule;
 import org.olat.modules.adobeconnect.model.AdobeConnectErrors;
 import org.olat.modules.adobeconnect.model.AdobeConnectSco;
 import org.olat.repository.RepositoryEntry;
@@ -56,6 +57,8 @@ public class EditAdobeConnectMeetingController extends FormBasicController {
 	
 	private TextElement nameEl;
 	private TextElement descriptionEl;
+	private TextElement leadTimeEl;
+	private TextElement followupTimeEl;
 	private DateChooser startDateEl;
 	private DateChooser endDateEl;
 	private MultipleSelectionElement permanentEl;
@@ -67,6 +70,8 @@ public class EditAdobeConnectMeetingController extends FormBasicController {
 	private final AdobeConnectMeetingDefaultConfiguration configuration;
 	private AdobeConnectMeeting meeting;
 	
+	@Autowired
+	private AdobeConnectModule adobeConnectModule;
 	@Autowired
 	private AdobeConnectManager adobeConnectManager;
 	
@@ -99,6 +104,10 @@ public class EditAdobeConnectMeetingController extends FormBasicController {
 		String name = meeting == null ? "" : meeting.getName();
 		nameEl = uifactory.addTextElement("meeting.name", "meeting.name", 128, name, formLayout);
 		nameEl.setMandatory(true);
+		if(!StringHelper.containsNonWhitespace(name)) {
+			nameEl.setFocus(true);
+		}
+		
 		String description = meeting == null ? "" : meeting.getDescription();
 		descriptionEl = uifactory.addTextAreaElement("meeting.description", "meeting.description", 2000, 8, 72, false, false, description, formLayout);
 		
@@ -118,17 +127,32 @@ public class EditAdobeConnectMeetingController extends FormBasicController {
 		String[] permValues = new String[] { translate("meeting.permanent.on") };
 		permanentEl = uifactory.addCheckboxesHorizontal("meeting.permanent", formLayout, permKeys, permValues);
 		permanentEl.addActionListener(FormEvent.ONCHANGE);
+		boolean permanent = meeting == null ? adobeConnectModule.isSingleMeetingMode() : meeting.isPermanent();
+		permanentEl.select(permKeys[0], permanent);
+		boolean permanentDisabled = adobeConnectModule.isCreateMeetingImmediately()
+				|| (meeting != null && StringHelper.containsNonWhitespace(meeting.getScoId()));
+		// if single mode -> always permanent
+		// if sco linked -> cannot changed it
+		permanentEl.setEnabled(!permanentDisabled);
+		permanentEl.setHelpTextKey("meeting.permanent.explain", null);
 
 		Date startDate = meeting == null ? null : meeting.getStartDate();
 		startDateEl = uifactory.addDateChooser("meeting.start", "meeting.start", startDate, formLayout);
 		startDateEl.setMandatory(true);
 		startDateEl.setDateChooserTimeEnabled(true);
+		
+		String leadtime = meeting == null ? null : Long.toString(meeting.getLeadTime());
+		leadTimeEl = uifactory.addTextElement("meeting.leadTime", 8, leadtime, formLayout);
+		
 		Date endDate = meeting == null ? null : meeting.getEndDate();
 		endDateEl = uifactory.addDateChooser("meeting.end", "meeting.end", endDate, formLayout);
 		endDateEl.setMandatory(true);
 		endDateEl.setDefaultValue(startDateEl);
 		endDateEl.setDateChooserTimeEnabled(true);
 		
+		String followup = meeting == null ? null : Long.toString(meeting.getFollowupTime());
+		followupTimeEl = uifactory.addTextElement("meeting.followupTime", 8, followup, formLayout);
+		
 		FormLayoutContainer buttonLayout = FormLayoutContainer.createButtonLayout("buttons", getTranslator());
 		formLayout.add("buttons", buttonLayout);
 		uifactory.addFormCancelButton("cancel", buttonLayout, ureq, getWindowControl());
@@ -137,8 +161,8 @@ public class EditAdobeConnectMeetingController extends FormBasicController {
 	
 	private void updateUI() {
 		boolean permanent = permanentEl.isAtLeastSelected(1);
-		startDateEl.setVisible(!permanent);
-		endDateEl.setVisible(!permanent);
+		startDateEl.setMandatory(!permanent);
+		endDateEl.setMandatory(!permanent);
 	}
 	
 	@Override
@@ -178,6 +202,18 @@ public class EditAdobeConnectMeetingController extends FormBasicController {
 			}
 		}
 		
+		allOk &= validateTime(leadTimeEl);
+		allOk &= validateTime(followupTimeEl);
+		return allOk;
+	}
+	
+	private boolean validateTime(TextElement el) {
+		boolean allOk = true;
+		el.clearError();
+		if(StringHelper.containsNonWhitespace(el.getValue()) && !StringHelper.isLong(el.getValue())) {
+			el.setErrorKey("form.error.nointeger", null);
+			allOk &= false;
+		}
 		return allOk;
 	}
 
@@ -193,23 +229,36 @@ public class EditAdobeConnectMeetingController extends FormBasicController {
 	protected void formOK(UserRequest ureq) {
 		AdobeConnectErrors errors = new AdobeConnectErrors();
 
-		Date startDate = null;
-		Date endDate = null;
-		if(!permanentEl.isAtLeastSelected(1)) {
-			startDate = startDateEl.getDate();
-			endDate = endDateEl.getDate();
+		Date startDate = startDateEl.getDate();
+		Date endDate = endDateEl.getDate();
+		boolean permanent;	
+		if(permanentEl.isVisible()) {
+			permanent = permanentEl.isAtLeastSelected(1);
+		} else {
+			permanent = true;
+		}
+		
+		long leadTime = 0;
+		if(leadTimeEl.isVisible() && StringHelper.isLong(leadTimeEl.getValue())) {
+			leadTime = Long.valueOf(leadTimeEl.getValue());
 		}
+		long followupTime = 0;
+		if(followupTimeEl.isVisible() && StringHelper.isLong(followupTimeEl.getValue())) {
+			followupTime = Long.valueOf(followupTimeEl.getValue());
+		}
+		
 		String templateId = null;
 		if(templateEl.isVisible() && StringHelper.containsNonWhitespace(templateEl.getSelectedKey())) {
 			templateId = templateEl.getSelectedKey();
 		}
-		
+
 		if(meeting == null) {
-			adobeConnectManager.createMeeting(nameEl.getValue(), descriptionEl.getValue(), templateId,
-					startDate, endDate, getLocale(), configuration.isAllowGuestAccess(), entry, subIdent, businessGroup, getIdentity(), errors);
+			adobeConnectManager.createMeeting(nameEl.getValue(), descriptionEl.getValue(), templateId, permanent,
+					startDate, leadTime, endDate, followupTime, getLocale(), configuration.isAllowGuestAccess(),
+					entry, subIdent, businessGroup, getIdentity(), errors);
 		} else {
 			adobeConnectManager.updateMeeting(meeting, nameEl.getValue(), descriptionEl.getValue(),
-					templateId, startDate, endDate, errors);
+					templateId, permanent, startDate, leadTime, endDate, followupTime, errors);
 		}
 		
 		if(errors.hasErrors()) {
diff --git a/src/main/java/org/olat/modules/adobeconnect/ui/_content/meeting.html b/src/main/java/org/olat/modules/adobeconnect/ui/_content/meeting.html
index b582aed5942..4cfe9e672ff 100644
--- a/src/main/java/org/olat/modules/adobeconnect/ui/_content/meeting.html
+++ b/src/main/java/org/olat/modules/adobeconnect/ui/_content/meeting.html
@@ -11,32 +11,42 @@
 #if($r.isTrue($ended))
 	<div class="o_block_large o_warning">$r.translate("meeting.ended")</div>
 #end
-#if($r.isFalse($validMeeting))
-	<div class="o_block_large o_error">$r.translate("error.invalid.meeting")</div>
-#end
 
-<div class="o_button_group">
-#if($r.available("meeting.start.button") && $r.visible("meeting.start.button"))
-	$r.render("meeting.start.button")
-#end
-#if($r.available("meeting.register.button") && $r.visible("meeting.register.button"))
-	$r.render("meeting.register.button")
-#end
-#if($r.available("meeting.join.button") && $r.visible("meeting.join.button"))
-	$r.render("meeting.join.button")
-#end
-</div>
-#if($r.visible("meeting.share.documents") || $r.visible("meetingContents"))
-<fieldset>
-	<legend>$r.translate("meetings.content")</legend>
-	#if($r.visible("meeting.share.documents"))
-	<div class="o_button_group o_button_group_right">
-		$r.render("meeting.share.documents")
-	</div>
+#if($r.isTrue($invalidProvider))
+	<div class="o_block_large o_error">$r.translate("error.invalid.meeting")</div>
+#else
+	#if($r.isFalse($meetingsExists) || $r.isTrue($notStarted))
+	<div class="o_block_large o_info">$r.translate("meeting.create.intro")</div>
 	#end
-	#if($r.isTrue($notRegistered))
-		<div class="o_warning">$r.translate("warning.not.registered.shared.documents")</div>
+	
+	<div class="o_button_group">
+	#if($r.available("meeting.create.button") && $r.visible("meeting.create.button"))
+		$r.render("meeting.create.button")
 	#end
-	$r.render("meetingContents")
-</fieldset>
-#end
\ No newline at end of file
+	#if($r.available("meeting.start.button") && $r.visible("meeting.start.button"))
+		$r.render("meeting.start.button")
+	#end
+	#if($r.available("meeting.register.button") && $r.visible("meeting.register.button"))
+		$r.render("meeting.register.button")
+	#end
+	#if($r.available("meeting.join.button") && $r.visible("meeting.join.button"))
+		$r.render("meeting.join.button")
+	#end
+	</div>
+	
+	#if($r.isTrue($meetingsExists) && ($r.visible("meeting.share.documents") || $r.visible("meetingContents")))
+	<fieldset>
+		<legend>$r.translate("meetings.content")</legend>
+		#if($r.visible("meeting.share.documents"))
+		<div class="o_button_group o_button_group_right">
+			$r.render("meeting.share.documents")
+		</div>
+		#end
+		#if($r.isTrue($notRegistered))
+			<div class="o_warning">$r.translate("warning.not.registered.shared.documents")</div>
+		#end
+		$r.render("meetingContents")
+	</fieldset>
+	#end
+#end
+
diff --git a/src/main/java/org/olat/modules/adobeconnect/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/modules/adobeconnect/ui/_i18n/LocalStrings_de.properties
index 7e72617918b..385f08bc21b 100644
--- a/src/main/java/org/olat/modules/adobeconnect/ui/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/modules/adobeconnect/ui/_i18n/LocalStrings_de.properties
@@ -33,14 +33,20 @@ error.prefix=Ein Fehler ist aufgetreten\:
 error.rangeError=Datumsbereich ist nicht g\u00FCltig.
 error.start.after.end=Das Datum f\u00FCr das Ende des Meetings darf nicht vor dem Beginn Datum sein.
 error.unkown=Unerwartete Fehler
+meeting.create.intro=Das Meeting wurde noch nicht er\u00f6ffnet. Teilnehmer k\u00F6nnen den Raum f\u00fcr ein geplantes Meeting ggf. nicht betreten.
+meeting.create.button=Meeting er\u00f6ffnen
 meeting.deleted=Meeting wurde gel\u00F6scht.
 meeting.description=Beschreibung
 meeting.end=Enddatum
 meeting.ended=Meeting ist schon beendet.
 meeting.join.button=Meeting beitreten
+meeting.followupTime=Nachlaufzeit (Min.)
+meeting.leadTime=Vorlaufzeit (Min.)
+meeting.go.button=Zum Meeting Raum gehen
 meeting.name=Name
 meeting.permanent=Typ
 meeting.permanent.on=Dauernd
+meeting.permanent.explain=Dauernd Meetings werden den gleichen Meeting Raum in einem Kursbaustein oder ein Gruppe teilen. 
 meeting.register.button=Anmelden
 meeting.resource=Resource
 meeting.share.documents=Dateien bereitstellen
@@ -59,8 +65,21 @@ no.meeting.configured=Es sind noch keine Meetings konfiguriert.
 no.shared.contents=Keine Meeting Dateien verf\u00FCgbar.
 no.template=Keine Vorlage
 no.upcoming.meetings=Sie haben keine zuk\u00FCnftigen Meetings.
+option.accountid=Konto ID
+option.accountid.explain=Konto ID ist nicht erfolderlich. Wenn Konto ID nicht spezifieziert ist, wird der Konto von dem oben definierten Benutzer ben\u00FCtzt.
 option.adminlogin=Benutzername
 option.adminpassword=Passwort
 option.baseurl=URL Adobe Connect Server
 option.baseurl.example=https\://meet73287594.adobeconnect.com/api/xml
+option.clean.meetings=Meetings aufra\u00FCmen (Tage)
+option.dont.clean.meetings=Nie
+option.create.meeting=Erstellungs von Meetings
+option.create.meeting.immediately=Sofort
+option.create.meeting.differed=Differed
+option.single.meeting=Verteilte Meetings
+option.single.meeting.single=Erstellt nur ein Meeting Raum pro Kursbaustein oder Gruppe
+option.single.meeting.perdate=Erstellt ein Meeting Raum pro Datum
+table.header.permanent=Dauernd
+warning.no.access=Sie k\u00F6nnen noch nicht den Meeting beitreten.
+warning.no.meeting=Das Meeting wurde gel\u00F6scht.
 warning.not.registered.shared.documents=Nur die Personen die sich an den Meeting angemeldet haben d\u00FCrfen die Dokumenten ansehen.
diff --git a/src/main/java/org/olat/modules/adobeconnect/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/modules/adobeconnect/ui/_i18n/LocalStrings_en.properties
index 76c30e7a3ae..662b7e301a1 100644
--- a/src/main/java/org/olat/modules/adobeconnect/ui/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/modules/adobeconnect/ui/_i18n/LocalStrings_en.properties
@@ -33,14 +33,20 @@ error.prefix=An error happened\:
 error.rangeError=The range is not valid.
 error.start.after.end=The end date of the meeting must not be before the start date.
 error.unkown=Unkown error
+meeting.create.button=Open the meeting
+meeting.create.intro=The meeting has not been opened, yet. Participants are not able to enter the classroom for a meeting.
 meeting.deleted=Meeting successfully deleted.
 meeting.description=Description
 meeting.end=End date
 meeting.ended=Meeting already ended.
 meeting.join.button=Join the meeting
+meeting.leadTime=Prep time (min.)
+meeting.followupTime=Follow-up (min.)
+meeting.go.button=Go to the meeting room
 meeting.name=Name
 meeting.permanent=Typ
-meeting.permanent.on=Dauernd
+meeting.permanent.on=Permanent
+meeting.permanent.explain=Permanent meetings will share the same meeting room within a course element or a group.
 meeting.register.button=Register
 meeting.resource=Resource
 meeting.share.documents=Share files
@@ -56,11 +62,24 @@ meetings.title=Meetings
 meetings.upcoming=Upcoming meetings
 no.contents=This meeting doesn't have any content
 no.meeting.configured=No meetings have been configured yet.
-no.shared.contents=No future meetings scheduled.
+no.shared.contents=No shared documents available.
 no.template=No template
 no.upcoming.meetings=You don't have any upcoming meeting.
+option.accountid=Account ID
+option.accountid.explain=Account ID is optional. If the account ID is not specified, the account of the user above will be used.
 option.adminlogin=Username
 option.adminpassword=Password
 option.baseurl=URL Adobe Connect Server
 option.baseurl.example=https\://meet73287594.adobeconnect.com/api/xml
+option.clean.meetings=Clean-up meetings (days)
+option.dont.clean.meetings=Never
+option.create.meeting=Create meetings
+option.create.meeting.immediately=Immediately
+option.create.meeting.differed=Differed
+option.single.meeting=Shared meetings
+option.single.meeting.single=Create only one meeting room per course element or group
+option.single.meeting.perdate=Create a meeting room per date
+table.header.permanent=Permanent
+warning.no.access=You cannot access the meeting yet.
+warning.no.meeting=The meeting has been deleted.
 warning.not.registered.shared.documents=Only the persons which attended the meeting are allowed to open the shared documents.
diff --git a/src/main/java/org/olat/modules/adobeconnect/ui/_i18n/LocalStrings_fr.properties b/src/main/java/org/olat/modules/adobeconnect/ui/_i18n/LocalStrings_fr.properties
index ed9ca1242bb..5ae779e3b78 100644
--- a/src/main/java/org/olat/modules/adobeconnect/ui/_i18n/LocalStrings_fr.properties
+++ b/src/main/java/org/olat/modules/adobeconnect/ui/_i18n/LocalStrings_fr.properties
@@ -33,11 +33,15 @@ error.prefix=Une erreur s'est produite\:
 error.rangeError=L'intervalle n'est pas valide.
 error.start.after.end=La date de fin du rendez-vous ne peut se trouver avant la date de d\u00E9but.
 error.unkown=Erreur inconnue
+meeting.create.button=Ouvrir le meeting
+meeting.create.intro=Le meeting n'est pas encore disponible. Les participants \u00E0 cette classe ne peuvent pas encore y acc\u00E9der.
 meeting.deleted=Le meeting a \u00E9t\u00E9 effac\u00E9 avec succ\u00E8s.
 meeting.description=Description
 meeting.end=Date de fin
 meeting.ended=Le meeting est termin\u00E9.
 meeting.join.button=Rejoindre le meeting
+meeting.followupTime=P\u00E9riode de temporisation (min.)
+meeting.leadTime=Pr\u00E9paration (min.)
 meeting.name=Nom
 meeting.permanent=Type
 meeting.permanent.on=Permanent
@@ -56,11 +60,12 @@ meetings.title=Meetings
 meetings.upcoming=Meetings \u00E0 venir
 no.contents=Ce meeting n'a pas de contenu.
 no.meeting.configured=Aucun meeting n'a \u00E9t\u00E9 configur\u00E9 pour l'instant.
-no.shared.contents=Pas de meetings pr\u00E9vus \u00E0 l'avenir.
+no.shared.contents=Pas de documents partag\u00E9s.
 no.template=Aucun mod\u00E8le
 no.upcoming.meetings=Vous n'avez pas meeting pr\u00E9vu \u00E0 l'avenir.
 option.adminlogin=Nom d'utilisateur
 option.adminpassword=Mot de passe
 option.baseurl=URL du serveur Adobe Connect
 option.baseurl.example=https\://meet73287594.adobeconnect.com/api/xml
+warning.no.access=Vous ne pouvez pas encore acc\u00E9der au meeting.
 warning.not.registered.shared.documents=Seules les personnes qui se sont enregistr\u00E9s peuvent voir les documents partag\u00E9s.
diff --git a/src/main/java/org/olat/upgrade/_spring/databaseUpgradeContext.xml b/src/main/java/org/olat/upgrade/_spring/databaseUpgradeContext.xml
index 0085fd554ec..f0129fe3c21 100644
--- a/src/main/java/org/olat/upgrade/_spring/databaseUpgradeContext.xml
+++ b/src/main/java/org/olat/upgrade/_spring/databaseUpgradeContext.xml
@@ -188,6 +188,10 @@
 					<constructor-arg index="0" value="OLAT_14.0.0" />
 					<property name="alterDbStatements" value="alter_13_2_x_to_14_0_0.sql" />
 				</bean>
+				<bean id="database_upgrade_14_0_1" class="org.olat.upgrade.DatabaseUpgrade">
+					<constructor-arg index="0" value="OLAT_14.0.1" />
+					<property name="alterDbStatements" value="alter_14_0_0_to_14_0_1.sql" />
+				</bean>
 			</list>
 		</property>
 	</bean>
diff --git a/src/main/resources/database/mysql/alter_14_0_0_to_14_0_1.sql b/src/main/resources/database/mysql/alter_14_0_0_to_14_0_1.sql
new file mode 100644
index 00000000000..0a63ae9e9af
--- /dev/null
+++ b/src/main/resources/database/mysql/alter_14_0_0_to_14_0_1.sql
@@ -0,0 +1,8 @@
+-- Adobe Connect
+alter table o_aconnect_meeting add column a_leadtime bigint default 0 not null;
+alter table o_aconnect_meeting add column a_start_with_leadtime datetime;
+alter table o_aconnect_meeting add column a_followuptime bigint default 0 not null;
+alter table o_aconnect_meeting add column a_end_with_followuptime datetime;
+alter table o_aconnect_meeting add column a_opened bool default false not null;
+alter table o_aconnect_meeting add column a_template_id varchar(32) default null;
+alter table o_aconnect_meeting add column a_permanent bool default false not null;
\ No newline at end of file
diff --git a/src/main/resources/database/mysql/setupDatabase.sql b/src/main/resources/database/mysql/setupDatabase.sql
index 2724ce92ba9..e3feb3aa892 100644
--- a/src/main/resources/database/mysql/setupDatabase.sql
+++ b/src/main/resources/database/mysql/setupDatabase.sql
@@ -1165,8 +1165,15 @@ create table o_aconnect_meeting (
    a_env_name varchar(128) default null,
    a_name varchar(128) not null,
    a_description varchar(2000) default null,
+   a_permanent bool default false not null,
    a_start_date datetime default null,
+   a_leadtime bigint default 0 not null,
+   a_start_with_leadtime datetime,
    a_end_date datetime default null,
+   a_followuptime bigint default 0 not null,
+   a_end_with_followuptime datetime,
+   a_opened bool default false not null,
+   a_template_id varchar(32) default null,
    a_shared_documents varchar(2000) default null,
    fk_entry_id bigint default null,
    a_sub_ident varchar(64) default null,
diff --git a/src/main/resources/database/oracle/alter_14_0_0_to_14_0_1.sql b/src/main/resources/database/oracle/alter_14_0_0_to_14_0_1.sql
new file mode 100644
index 00000000000..1c4d9148792
--- /dev/null
+++ b/src/main/resources/database/oracle/alter_14_0_0_to_14_0_1.sql
@@ -0,0 +1,8 @@
+-- Adobe Connect
+alter table o_aconnect_meeting add a_leadtime number(20) default 0 not null;
+alter table o_aconnect_meeting add a_start_with_leadtime timestamp;
+alter table o_aconnect_meeting add a_followuptime number(20) default 0 not null;
+alter table o_aconnect_meeting add a_end_with_followuptime timestamp;
+alter table o_aconnect_meeting add a_opened number default 0 not null;
+alter table o_aconnect_meeting add a_template_id varchar(32) default null;
+alter table o_aconnect_meeting add a_permanent number default 0 not null;
diff --git a/src/main/resources/database/oracle/setupDatabase.sql b/src/main/resources/database/oracle/setupDatabase.sql
index 8d57a386cf4..e479ae1928e 100644
--- a/src/main/resources/database/oracle/setupDatabase.sql
+++ b/src/main/resources/database/oracle/setupDatabase.sql
@@ -1230,8 +1230,15 @@ create table o_aconnect_meeting (
    a_env_name varchar(128) default null,
    a_name varchar(128) not null,
    a_description varchar(2000) default null,
+   a_permanent number default 0 not null,
    a_start_date timestamp default null,
+   a_leadtime number(20) default 0 not null,
+   a_start_with_leadtime timestamp,  
    a_end_date timestamp default null,
+   a_followuptime number(20) default 0 not null,
+   a_end_with_followuptime timestamp,
+   a_opened number default 0 not null,
+   a_template_id varchar(32) default null,
    a_shared_documents varchar(2000) default null,
    fk_entry_id number(20) default null,
    a_sub_ident varchar(64) default null,
diff --git a/src/main/resources/database/postgresql/alter_14_0_0_to_14_0_1.sql b/src/main/resources/database/postgresql/alter_14_0_0_to_14_0_1.sql
new file mode 100644
index 00000000000..df0fd542f10
--- /dev/null
+++ b/src/main/resources/database/postgresql/alter_14_0_0_to_14_0_1.sql
@@ -0,0 +1,8 @@
+-- Adobe Connect
+alter table o_aconnect_meeting add column a_leadtime bigint default 0 not null;
+alter table o_aconnect_meeting add column a_start_with_leadtime timestamp;
+alter table o_aconnect_meeting add column a_followuptime bigint default 0 not null;
+alter table o_aconnect_meeting add column a_end_with_followuptime timestamp;
+alter table o_aconnect_meeting add column a_opened bool default false not null;
+alter table o_aconnect_meeting add column a_template_id varchar(32) default null;
+alter table o_aconnect_meeting add column a_permanent bool default false not null;
\ No newline at end of file
diff --git a/src/main/resources/database/postgresql/setupDatabase.sql b/src/main/resources/database/postgresql/setupDatabase.sql
index 4a192014bbb..b517b27b7d5 100644
--- a/src/main/resources/database/postgresql/setupDatabase.sql
+++ b/src/main/resources/database/postgresql/setupDatabase.sql
@@ -1190,8 +1190,15 @@ create table o_aconnect_meeting (
    a_env_name varchar(128) default null,
    a_name varchar(128) not null,
    a_description varchar(2000) default null,
+   a_permanent bool default false not null,
    a_start_date timestamp default null,
+   a_leadtime bigint default 0 not null,
+   a_start_with_leadtime timestamp,
    a_end_date timestamp default null,
+   a_followuptime bigint default 0 not null,
+   a_end_with_followuptime timestamp,
+   a_opened bool default false not null,
+   a_template_id varchar(32) default null,
    a_shared_documents varchar(2000) default null,
    fk_entry_id int8 default null,
    a_sub_ident varchar(64) default null,
diff --git a/src/test/java/org/olat/modules/adobeconnect/manager/AdobeConnectMeetingDAOTest.java b/src/test/java/org/olat/modules/adobeconnect/manager/AdobeConnectMeetingDAOTest.java
index e984e5ffae0..d009d965724 100644
--- a/src/test/java/org/olat/modules/adobeconnect/manager/AdobeConnectMeetingDAOTest.java
+++ b/src/test/java/org/olat/modules/adobeconnect/manager/AdobeConnectMeetingDAOTest.java
@@ -19,6 +19,7 @@
  */
 package org.olat.modules.adobeconnect.manager;
 
+import java.util.Calendar;
 import java.util.Date;
 import java.util.List;
 import java.util.UUID;
@@ -51,7 +52,7 @@ public class AdobeConnectMeetingDAOTest extends OlatTestCase {
 	
 	@Test
 	public void createMeeting() {
-		AdobeConnectMeeting meeting = adobeConnectMeetingDao.createMeeting("New meeting", "Very interessant", new Date(), new Date(), "sco-id", "folder-id", "DFN", null, null, null);
+		AdobeConnectMeeting meeting = adobeConnectMeetingDao.createMeeting("New meeting", "Very interessant", false, new Date(), 15, new Date(), 15, null, "sco-id", "folder-id", "DFN", null, null, null);
 		dbInstance.commit();
 		Assert.assertNotNull(meeting);
 		Assert.assertNotNull(meeting.getKey());
@@ -70,7 +71,7 @@ public class AdobeConnectMeetingDAOTest extends OlatTestCase {
 		RepositoryEntry entry = JunitTestHelper.createAndPersistRepositoryEntry();
 		String subIdent = UUID.randomUUID().toString();
 		
-		AdobeConnectMeeting meeting = adobeConnectMeetingDao.createMeeting("Course meeting", "Annoying", new Date(), new Date(), "sco-id", "folder-id", "DFN", entry, subIdent, null);
+		AdobeConnectMeeting meeting = adobeConnectMeetingDao.createMeeting("Course meeting", "Annoying", false, new Date(), 10, new Date(), 10, null, "sco-id", "folder-id", "DFN", entry, subIdent, null);
 		dbInstance.commit();
 		Assert.assertNotNull(meeting);
 		Assert.assertNotNull(meeting.getKey());
@@ -92,7 +93,7 @@ public class AdobeConnectMeetingDAOTest extends OlatTestCase {
 		RepositoryEntry entry = JunitTestHelper.createAndPersistRepositoryEntry();
 		String subIdent = UUID.randomUUID().toString();
 		
-		AdobeConnectMeeting meeting = adobeConnectMeetingDao.createMeeting("Key meeting", "Primary", new Date(), new Date(), "sco-pid", "folder-id", null, entry, subIdent, null);
+		AdobeConnectMeeting meeting = adobeConnectMeetingDao.createMeeting("Key meeting", "Primary", false, new Date(), 10, new Date(), 10, null, "sco-pid", "folder-id", null, entry, subIdent, null);
 		dbInstance.commitAndCloseSession();
 		
 		// load the meeting
@@ -112,14 +113,29 @@ public class AdobeConnectMeetingDAOTest extends OlatTestCase {
 		Assert.assertEquals(subIdent, meeting.getSubIdent());
 		Assert.assertNull(meeting.getBusinessGroup());
 	}
+	
+	@Test
+	public void getAllMeetings() {
+		RepositoryEntry entry = JunitTestHelper.createAndPersistRepositoryEntry();
+		String subIdent = UUID.randomUUID().toString();
+		
+		AdobeConnectMeeting meeting1 = adobeConnectMeetingDao.createMeeting("Course A", "Primary", false, new Date(), 5, new Date(), 5, null, "sco-cid-1", "folder-id", null, entry, subIdent, null);
+		AdobeConnectMeeting meeting2 = adobeConnectMeetingDao.createMeeting("Course A", "Primary", false, new Date(), 5, new Date(), 5, "tmp-id-1", "sco-cid-2", "folder-id", null, entry, subIdent, null);
+		dbInstance.commitAndCloseSession();
+		
+		List<AdobeConnectMeeting> allMeetings = adobeConnectMeetingDao.getAllMeetings();
+		Assert.assertTrue(allMeetings.size() >= 2);
+		Assert.assertTrue(allMeetings.contains(meeting1));
+		Assert.assertTrue(allMeetings.contains(meeting2));
+	}
 
 	@Test
 	public void getMeetings_repositoryEntry() {
 		RepositoryEntry entry = JunitTestHelper.createAndPersistRepositoryEntry();
 		String subIdent = UUID.randomUUID().toString();
 		
-		AdobeConnectMeeting meeting1 = adobeConnectMeetingDao.createMeeting("Course A", "Primary", new Date(), new Date(), "sco-cid-1", "folder-id", null, entry, subIdent, null);
-		AdobeConnectMeeting meeting2 = adobeConnectMeetingDao.createMeeting("Course A", "Primary", new Date(), new Date(), "sco-cid-2", "folder-id", null, entry, subIdent, null);
+		AdobeConnectMeeting meeting1 = adobeConnectMeetingDao.createMeeting("Course A", "Primary", true, new Date(), 5, new Date(), 5, null, "sco-cid-1", "folder-id", null, entry, subIdent, null);
+		AdobeConnectMeeting meeting2 = adobeConnectMeetingDao.createMeeting("Course A", "Primary", true, new Date(), 5, new Date(), 5, "tmp-id-1", "sco-cid-2", "folder-id", null, entry, subIdent, null);
 		dbInstance.commitAndCloseSession();
 		
 		// load meetings
@@ -134,8 +150,8 @@ public class AdobeConnectMeetingDAOTest extends OlatTestCase {
 		BusinessGroup group = businessGroupDao.createAndPersist(null, "Connected group", "Adobe connected group", -1, -1, false, false, false, false, false);
 		dbInstance.commit();
 		
-		AdobeConnectMeeting meeting1 = adobeConnectMeetingDao.createMeeting("Course A", "Primary", new Date(), new Date(), "sco-cid-1", "folder-id", null, null, null, group);
-		AdobeConnectMeeting meeting2 = adobeConnectMeetingDao.createMeeting("Course A", "Primary", new Date(), new Date(), "sco-cid-2", "folder-id", null, null, null, group);
+		AdobeConnectMeeting meeting1 = adobeConnectMeetingDao.createMeeting("Course A", "Primary", false, new Date(), 5, new Date(), 5, null, "sco-cid-1", "folder-id", null, null, null, group);
+		AdobeConnectMeeting meeting2 = adobeConnectMeetingDao.createMeeting("Course A", "Primary", false, new Date(), 5, new Date(), 5, null, "sco-cid-2", "folder-id", null, null, null, group);
 		dbInstance.commitAndCloseSession();
 		
 		// load meetings
@@ -145,5 +161,24 @@ public class AdobeConnectMeetingDAOTest extends OlatTestCase {
 		Assert.assertTrue(meetings.contains(meeting2));
 	}
 	
-	
+	@Test
+	public void getMeetingsBefore() {
+		RepositoryEntry entry = JunitTestHelper.createAndPersistRepositoryEntry();
+		String subIdent = UUID.randomUUID().toString();
+		
+		Calendar cal = Calendar.getInstance();
+		cal.add(Calendar.DATE, -3);
+		Date oldDate = cal.getTime();
+		AdobeConnectMeeting oldMeeting = adobeConnectMeetingDao.createMeeting("Course old", "Primary", false, oldDate, 5, oldDate, 5, null, "sco-cid-1", "folder-id", null, entry, subIdent, null);
+		AdobeConnectMeeting newMeeting = adobeConnectMeetingDao.createMeeting("Course new", "Primary", false, new Date(), 5, new Date(), 5, null, "sco-cid-1", "folder-id", null, entry, subIdent, null);
+		dbInstance.commitAndCloseSession();
+		
+		cal.setTime(new Date());
+		cal.add(Calendar.DATE, -1);
+		Date beforeDate = cal.getTime();
+		List<AdobeConnectMeeting> allMeetings = adobeConnectMeetingDao.getMeetingsBefore(beforeDate);
+		Assert.assertTrue(allMeetings.size() >= 1);
+		Assert.assertTrue(allMeetings.contains(oldMeeting));
+		Assert.assertFalse(allMeetings.contains(newMeeting));
+	}
 }
-- 
GitLab