/** * <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.core.commons.controllers.resume; import java.util.ArrayList; import java.util.Calendar; import java.util.List; import org.olat.NewControllerFactory; import org.olat.admin.landingpages.LandingPagesModule; import org.olat.admin.landingpages.model.Rules; import org.olat.core.gui.UserRequest; import org.olat.core.gui.WindowManager; import org.olat.core.gui.components.Component; import org.olat.core.gui.components.panel.Panel; import org.olat.core.gui.components.velocity.VelocityContainer; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.controller.BasicController; import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController; import org.olat.core.gui.control.generic.wizard.WizardInfoController; import org.olat.core.id.context.BusinessControlFactory; import org.olat.core.id.context.ContextEntry; import org.olat.core.id.context.HistoryManager; import org.olat.core.id.context.HistoryModule; import org.olat.core.id.context.HistoryPoint; import org.olat.core.util.StringHelper; import org.olat.core.util.UserSession; import org.olat.core.util.Util; import org.olat.core.util.prefs.Preferences; import org.olat.login.AfterLoginInterceptionManager; import org.olat.login.LoginInterceptorConfiguration; import org.olat.login.SupportsAfterLoginInterceptor; import org.olat.properties.Property; import org.olat.properties.PropertyManager; import org.springframework.beans.factory.annotation.Autowired; /** * * Initial date: 05.11.2015<br> * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ public class ResumeSessionController extends BasicController { private static final String PROPERTY_CAT = "afterLogin"; private Panel currentPanel; private VelocityContainer mainVC; private Interceptor currentInterceptor; private ResumeController resumeCtrl; private CloseableModalController cmc; private WizardInfoController wizardCtrl; private final List<Property> preferencesList; private final List<Interceptor> interceptors; private final Redirect redirect; @Autowired private PropertyManager pm; @Autowired private LandingPagesModule lpModule; @Autowired private HistoryModule historyModule; @Autowired private HistoryManager historyManager; @Autowired private AfterLoginInterceptionManager loginInterceptorsManager; public ResumeSessionController(UserRequest ureq, WindowControl wControl) { super(ureq, wControl); setTranslator(Util.createPackageTranslator(AfterLoginInterceptionManager.class, getLocale(), getTranslator())); List<LoginInterceptorConfiguration> configurations = loginInterceptorsManager.getInterceptorsConfiguration(); preferencesList = pm.listProperties(ureq.getIdentity(), null, null, null, PROPERTY_CAT, null); interceptors = new ArrayList<>(); for(LoginInterceptorConfiguration configuration:configurations) { if(configuration.getCreator() == null) continue; Controller interceptorCtrl = configuration.getCreator().createController(ureq, wControl); //check redo timeout / state if(interceptorCtrl != null && isInterceptionNeeded(interceptorCtrl, configuration)) { String i18nKey = configuration.getI18nIntroKey(); boolean forceUser = configuration.isForceUser(); // check if interception criteria is needed if(interceptorCtrl instanceof SupportsAfterLoginInterceptor) { SupportsAfterLoginInterceptor loginInterceptor = (SupportsAfterLoginInterceptor)interceptorCtrl; if(loginInterceptor.isUserInteractionRequired(ureq)) { interceptors.add(new Interceptor(interceptorCtrl, i18nKey, forceUser)); } else { interceptorCtrl.dispose(); } } else { interceptors.add(new Interceptor(interceptorCtrl, i18nKey, forceUser)); } } } //can add disclaimer? redirect = isResumeInteractionRequired(ureq); if(redirect.isInterceptionRequired()) { //add an ad hoc interceptor resumeCtrl = new ResumeController(ureq, getWindowControl(), redirect); listenTo(resumeCtrl); interceptors.add(new Interceptor(resumeCtrl, "org.olat.core.commons.controllers.resume:resume", false)); } if(interceptors.isEmpty()) { if(StringHelper.containsNonWhitespace(redirect.getRedirectUrl())) { redirect(ureq, redirect.getRedirectUrl()); } terminateInterception(ureq); } else { mainVC = createVelocityContainer("resume_session"); currentPanel = new Panel("resumePanel"); mainVC.put("actualPanel", currentPanel); if(interceptors.size() > 1) { wizardCtrl = new WizardInfoController(ureq, interceptors.size()); listenTo(wizardCtrl); mainVC.put("wizard", wizardCtrl.getInitialComponent()); mainVC.contextPut("ctrlCount", interceptors.size()); } pushNextInterceptor(ureq); cmc = new CloseableModalController(getWindowControl(), translate("close"), mainVC, true, translate("runonce.title"), false); cmc.activate(); listenTo(cmc); } } /** * Return true if a redirect happen * @return */ public boolean redirect() { return redirect != null && !redirect.isInterceptionRequired() && StringHelper.containsNonWhitespace(redirect.getRedirectUrl()); } public boolean userInteractionNeeded() { return interceptors.size() > 0; } @Override protected void doDispose() { // } @Override protected void event(UserRequest ureq, Component source, Event event) { // } @Override protected void event(UserRequest ureq, Controller source, Event event) { if (source == cmc && event == CloseableModalController.CLOSE_MODAL_EVENT) { // show warning if this is a task, where user is forced to do it if(currentInterceptor != null && currentInterceptor.isForceUserKey()) { showWarning("runonce.forced"); cmc.activate(); } } else if(resumeCtrl == source) { String cmd = event.getCommand(); if("landing".equals(cmd)) { launch(ureq, redirect.getLandingPage()); } else if("no".equals(cmd)) { //nothing to do } else if(StringHelper.containsNonWhitespace(redirect.getRedirectUrl())) { String bc = redirect.getFormattedRedirectUrl(); launch(ureq, bc); } terminateInterception(ureq); } else if (currentInterceptor != null && currentInterceptor.getController() == source) { if(event == Event.DONE_EVENT) { saveOrUpdatePropertyForController(source.getClass().getName()); pushNextInterceptor(ureq); } else if(event == Event.CANCELLED_EVENT) { if(!currentInterceptor.isForceUserKey()) { pushNextInterceptor(ureq); } } } super.event(ureq, source, event); } private boolean isInterceptionNeeded(Controller controller, LoginInterceptorConfiguration interceptor) { String ctrlName = controller.getClass().getName(); // check if the time between to appearance is ago if (interceptor.getRedoTimeout() != null) { // redo-timeout not yet over, so don't do again Long redoTimeout = interceptor.getRedoTimeout(); if (((Calendar.getInstance().getTimeInMillis() / 1000) - redoTimeout) < getLastRunTimeForController(ctrlName)) { controller.dispose(); return false; } // check if run already for non-recurring entries } else if (getRunStateForController(ctrlName)) { controller.dispose(); return false; } return true; } private Long getLastRunTimeForController(String ctrlName) { for (Property prop : preferencesList) { if (prop.getName().equals(ctrlName)) { return new Long(prop.getLastModified().getTime() / 1000); } } return new Long(0); } private boolean getRunStateForController(String ctrlName) { for (Property prop : preferencesList) { if (prop.getName().equals(ctrlName)) { return Boolean.parseBoolean(prop.getStringValue()); } } return false; } private void saveOrUpdatePropertyForController(String ctrlName) { for (Property prop : preferencesList) { if (prop.getName().equals(ctrlName)) { prop.setStringValue(Boolean.TRUE.toString()); pm.updateProperty(prop); return; } } Property prop = pm.createPropertyInstance(getIdentity(), null, null, PROPERTY_CAT, ctrlName, null, null, "true", null); pm.saveProperty(prop); } private void pushNextInterceptor(UserRequest ureq) { int currentCtrlIndex = -1; if(currentInterceptor != null) { currentCtrlIndex = interceptors.indexOf(currentInterceptor); removeAsListenerAndDispose(currentInterceptor.getController()); } if ((currentCtrlIndex + 1) < interceptors.size()) { int nextCtrlIndex = currentCtrlIndex + 1; if (interceptors.get(nextCtrlIndex) == null) { return; } if(wizardCtrl != null) { //actualCtrNr = ctrNr; wizardCtrl.setCurStep(nextCtrlIndex + 1); } currentInterceptor = interceptors.get(nextCtrlIndex); Controller currentCtrl = currentInterceptor.getController(); if (currentCtrl != null) { listenTo(currentCtrl); if (StringHelper.containsNonWhitespace(currentInterceptor.getI18nKey())) { String[] introComb = currentInterceptor.getI18nKey().split(":"); mainVC.contextPut("introPkg", introComb[0]); mainVC.contextPut("introKey", introComb[1]); } else { mainVC.contextRemove("introPkg"); mainVC.contextRemove("introKey"); } currentPanel.setContent(currentCtrl.getInitialComponent()); } } else { cmc.deactivate(); if(StringHelper.containsNonWhitespace(redirect.getRedirectUrl())) { String bc = redirect.getFormattedRedirectUrl(); launch(ureq, bc); redirect(ureq, redirect.getRedirectUrl()); } terminateInterception(ureq); } } private void terminateInterception(UserRequest ureq) { fireEvent(ureq, Event.DONE_EVENT); } private Redirect isResumeInteractionRequired(UserRequest ureq) { UserSession usess = ureq.getUserSession(); Redirect option; if(isREST(ureq)) { String url = getRESTRedirectURL(ureq); option = new Redirect(url); } else if(!historyModule.isResumeEnabled()) { String url = toUrl(getLandingBC(ureq)); option = new Redirect(url); } else if(usess.getRoles().isGuestOnly()) { String url = toUrl(getLandingBC(ureq)); option = new Redirect(url); } else { Preferences prefs = usess.getGuiPreferences(); String resumePrefs = (String)prefs.get(WindowManager.class, "resume-prefs"); if(!StringHelper.containsNonWhitespace(resumePrefs)) { resumePrefs = historyModule.getResumeDefaultSetting(); } if("none".equals(resumePrefs)) { String url = toUrl(getLandingBC(ureq)); option = new Redirect(url); } else if ("auto".equals(resumePrefs)) { HistoryPoint historyEntry = HistoryManager.getInstance().readHistoryPoint(ureq.getIdentity()); if(historyEntry != null && StringHelper.containsNonWhitespace(historyEntry.getBusinessPath())) { List<ContextEntry> cloneCes = BusinessControlFactory.getInstance().cloneContextEntries(historyEntry.getEntries()); String bc = BusinessControlFactory.getInstance().getAsRestPart(cloneCes, true); option = new Redirect(bc); } else { String url = toUrl(getLandingBC(ureq)); option = new Redirect(url); } } else if ("ondemand".equals(resumePrefs)) { HistoryPoint historyEntry = historyManager.readHistoryPoint(ureq.getIdentity()); if(historyEntry != null && StringHelper.containsNonWhitespace(historyEntry.getBusinessPath())) { List<ContextEntry> cloneCes = BusinessControlFactory.getInstance().cloneContextEntries(historyEntry.getEntries()); String url = BusinessControlFactory.getInstance().getAsRestPart(cloneCes, true); String landingPage = getLandingBC(ureq); option = new Redirect(url, landingPage); } else { String url = toUrl(getLandingBC(ureq)); option = new Redirect(url); } } else { String url = toUrl(getLandingBC(ureq)); option = new Redirect(url); } } return option; } private boolean isREST(UserRequest ureq) { UserSession usess = ureq.getUserSession(); if(usess.getEntry("AuthDispatcher:businessPath") != null) return true; return false; } private String getRESTRedirectURL(UserRequest ureq) { UserSession usess = ureq.getUserSession(); String url = (String)usess.getEntry("AuthDispatcher:businessPath"); List<ContextEntry> ces = BusinessControlFactory.getInstance().createCEListFromString(url); return BusinessControlFactory.getInstance().getAsRestPart(ces, true); } private String getLandingBC(UserRequest ureq) { Preferences prefs = ureq.getUserSession().getGuiPreferences(); String landingPage = (String)prefs.get(WindowManager.class, "landing-page"); if(StringHelper.containsNonWhitespace(landingPage)) { String path = Rules.cleanUpLandingPath(landingPage); if(StringHelper.containsNonWhitespace(path)) { landingPage = BusinessControlFactory.getInstance().formatFromURI(path); } } if(!StringHelper.containsNonWhitespace(landingPage)) { landingPage = lpModule.getRules().match(ureq.getUserSession()); } return landingPage; } private String toUrl(String businessPath) { String url = businessPath; if(StringHelper.containsNonWhitespace(url)) { if(url.startsWith("[")) { List<ContextEntry> ces = BusinessControlFactory.getInstance().createCEListFromString(url); url = BusinessControlFactory.getInstance().getAsRestPart(ces, true); } if(url.startsWith("/")) { url = url.substring(1, url.length()); } } return url; } private void launch(UserRequest ureq, String businessPath) { if(StringHelper.containsNonWhitespace(businessPath)) { try { //make the resume secure. If something fail, don't generate a red screen NewControllerFactory.getInstance().launch(businessPath, ureq, getWindowControl()); } catch (Exception e) { logError("Error while resuming", e); } } } private void redirect(UserRequest ureq, String url) { if(StringHelper.containsNonWhitespace(url)) { try { ureq.getUserSession().putEntry("redirect-bc", url); } catch (Exception e) { logError("Error while resuming", e); } } } public static class Redirect { private final String redirectUrl; private final String landingPagePath; private final boolean interception; public Redirect(String redirectUrl) { this.redirectUrl = redirectUrl; landingPagePath = null; interception = false; } public Redirect(String redirectUrl, String landingPagePath) { this.redirectUrl = redirectUrl; this.landingPagePath = landingPagePath; interception = true; } public boolean isInterceptionRequired() { return interception; } public String getRedirectUrl() { return redirectUrl; } /** * * @return A business path formatted like [xy:0] */ public String getFormattedRedirectUrl() { String bc = redirectUrl; if(bc.indexOf("]") < 0) { bc = BusinessControlFactory.getInstance().formatFromURI(bc); } return bc; } public String getLandingPage() { return landingPagePath; } } private static class Interceptor { private final String i18nKey; private final boolean forceUserKey; private final Controller controller; public Interceptor(Controller controller, String i18nKey, boolean forceUserKey) { this.controller = controller; this.i18nKey = i18nKey; this.forceUserKey = forceUserKey; } public String getI18nKey() { return i18nKey; } public boolean isForceUserKey() { return forceUserKey; } public Controller getController() { return controller; } } }