/**
* OLAT - Online Learning and Training<br>
* http://www.olat.org
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br>
* you may not use this file except in compliance with the License.<br>
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing,<br>
* software distributed under the License is distributed on an "AS IS" BASIS, <br>
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
* See the License for the specific language governing permissions and <br>
* limitations under the License.
* <p>
* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br>
* University of Zurich, Switzerland.
* <p>
*/
package org.olat.admin.registration;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.ParseException;
import java.util.Calendar;
import java.util.List;
import java.util.Properties;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.URIException;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PutMethod;
import org.apache.commons.httpclient.methods.StringRequestEntity;
import org.apache.commons.lang.math.RandomUtils;
import org.olat.basesecurity.Constants;
import org.olat.basesecurity.Manager;
import org.olat.basesecurity.ManagerFactory;
import org.olat.basesecurity.PermissionOnResourceable;
import org.olat.basesecurity.SecurityGroup;
import org.olat.core.CoreSpringFactory;
import org.olat.core.Version;
import org.olat.core.configuration.PersistedProperties;
import org.olat.core.configuration.PersistedPropertiesChangedEvent;
import org.olat.core.gui.control.Event;
import org.olat.core.helpers.Settings;
import org.olat.core.id.Identity;
import org.olat.core.logging.OLATRuntimeException;
import org.olat.core.manager.BasicManager;
import org.olat.core.util.CodeHelper;
import org.olat.core.util.StringHelper;
import org.olat.core.util.WebappHelper;
import org.olat.core.util.coordinate.CoordinatorManager;
import org.olat.core.util.event.GenericEventListener;
import org.olat.core.util.httpclient.HttpClientFactory;
import org.olat.core.util.i18n.I18nModule;
import org.olat.course.CourseModule;
import org.olat.group.BusinessGroup;
import org.olat.group.context.BGContextManager;
import org.olat.group.context.BGContextManagerImpl;
import org.olat.instantMessaging.InstantMessagingModule;
import org.olat.repository.RepositoryEntry;
import org.olat.repository.RepositoryManager;
import org.quartz.CronExpression;
import org.quartz.CronTrigger;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
/**
* Description:<br>
* This manager offers methods to store registration preferences and to register
* the installation on the olat.org server.
*
* <P>
* Initial Date: 12.12.2008 <br>
*
* @author gnaegi
*/
public class SystemRegistrationManager extends BasicManager implements GenericEventListener {
private static final String POST_PARAMETER_NAME = "registrationData";
private static SystemRegistrationManager INSTANCE = new SystemRegistrationManager();
// Version flag for data xml
private static final String VERSION = "1.0";
private static final String SCHEDULER_NAME = "system.registration";
// configuration keys in persisted properties
private static PersistedProperties persitedProperties;
public static final String CONF_KEY_SEND_STATISTICS = "sendStatistics";
public static final String CONF_KEY_PUBLISH_WEBSITE = "publishWebsite";
public static final String CONF_KEY_WEBSITE_DESCRIPTION = "websiteDescription";
public static final String CONF_KEY_NOTIFY_RELEASES = "notifyReleases";
public static final String CONF_KEY_EMAIL = "email";
// not configurable by user
public static final String CONF_KEY_REGISTRATION_CRON = "registrationCron";
public static final String CONF_KEY_IDENTIFYER = "instanceIdentifyer";
// Where to post the registration. Don't move this to a config, it should not
// be that easy to modify the registration server URL!
private static final String REGISTRATION_SERVER = "http://www.olat.org/olatregistration/registrations/";
//private static final String REGISTRATION_SERVER = "http://localhost:8088/olatregistration/registrations/";
//location described by language, e.g. "Winterthurerstrasse 190, Zürich", or "Dresden"....
public static final String CONF_KEY_LOCATION = "location";
// the geolocation derived with a google maps service for usage to place markers on a google map
public static final String CONF_KEY_LOCATION_COORDS="location_coords";
// on first registration request, the registration.olat.org creates a secret key - needed for future updates
private static final String CONF_SECRETKEY = "secret_key";
/**
* Use getInstance(), this is a singleton
*/
private SystemRegistrationManager() {
super();
initConfiguration();
}
/**
* Call this to shutdown the cron scheduler and remove cluster event listeners
* from the PersistedProperties infrastructure
*/
public void destroy() {
// remove properties
if (persitedProperties != null) {
persitedProperties.destroy();
}
// Stop registration job
Scheduler scheduler = (Scheduler) CoreSpringFactory.getBean("schedulerFactoryBean");
try {
scheduler.deleteJob(SCHEDULER_NAME, Scheduler.DEFAULT_GROUP);
} catch (SchedulerException e) {
logError("Could not shut down job::" + SCHEDULER_NAME, e);
}
}
/**
* Initialize the configuration, remove any old config that might exist
*/
private synchronized void initConfiguration() {
// Remove existing properties first
if (persitedProperties != null) {
persitedProperties.destroy();
}
// Initialize configuration properties
persitedProperties = new PersistedProperties(this);
persitedProperties.setBooleanPropertyDefault(CONF_KEY_SEND_STATISTICS, true);
persitedProperties.setBooleanPropertyDefault(CONF_KEY_PUBLISH_WEBSITE, false);
persitedProperties.setStringPropertyDefault(CONF_KEY_WEBSITE_DESCRIPTION, "");
persitedProperties.setBooleanPropertyDefault(CONF_KEY_NOTIFY_RELEASES, false);
persitedProperties.setStringPropertyDefault(CONF_KEY_EMAIL, WebappHelper.getMailConfig("mailSupport"));
// Check if cron property exist
if (persitedProperties.getStringPropertyValue(CONF_KEY_REGISTRATION_CRON, false) == null) {
String cronExpression = createCronTriggerExpression();
// persist so that next startup we have same trigger
persitedProperties.setStringProperty(CONF_KEY_REGISTRATION_CRON, cronExpression, true);
}
// Check if instance identifyer property exists
if (persitedProperties.getStringPropertyValue(CONF_KEY_IDENTIFYER, false) == null) {
String uniqueID = CodeHelper.getGlobalForeverUniqueID();
MessageDigest digester;
try {
digester = MessageDigest.getInstance("MD5");
digester.update(uniqueID.getBytes(),0,uniqueID.length());
persitedProperties.setStringProperty(CONF_KEY_IDENTIFYER, new BigInteger(1,digester.digest()).toString(16), true);
// trigger first execution of registration
//FIXME:FG:6.1: deferre this execution until framework is fully loaded
//sendRegistrationData();
} catch (NoSuchAlgorithmException e) {
// using no encoding instead
persitedProperties.setStringProperty(CONF_KEY_IDENTIFYER, uniqueID, true);
}
}
}
/**
* Helper method to create a cron trigger expression. The method makes sure
* that not every olat installation submits at the same time
*
* @return
*/
private String createCronTriggerExpression() {
// Create a random hour and minute for the cronjob so that not every
// installation registers at the same time
int min = RandomUtils.nextInt(59);
int hour = RandomUtils.nextInt(23);
int day = RandomUtils.nextInt(6);
String cronExpression = "0 " + min + " " + hour + " " + day + " * ?";
return cronExpression;
}
/**
* Singleton, use this to get a handle to the manager
*
* @return
*/
public static SystemRegistrationManager getInstance() {
return INSTANCE;
}
/**
* @return The persisted configuration
*/
PersistedProperties getRegistrationConfiguration() {
return persitedProperties;
}
String getLocationCoordinates(String textLocation){
String csvCoordinates = null;
if (textLocation == null || textLocation.length()==0) {
return null;
}
HttpClient client = HttpClientFactory.getHttpClientInstance();
String url = "http://maps.google.com/maps/geo";
NameValuePair[] nvps = new NameValuePair[5];
nvps[0] = new NameValuePair("q",textLocation);
nvps[1] = new NameValuePair("output","csv");
nvps[2] = new NameValuePair("oe","utf8");
nvps[3] = new NameValuePair("sensor","false");
nvps[4] = new NameValuePair("key","ABQIAAAAq5BZJrKbG-xh--W4MrciXRQZTOqTGVCcmpRMgrUbtlJvJ3buAhSfG7H7hgE66BCW17_gLyhitMNP4A");
GetMethod getCall = new GetMethod(url);
getCall.setQueryString(nvps);
try {
client.executeMethod(getCall);
String resp = null;
if(getCall.getStatusCode() == 200){
resp = getCall.getResponseBodyAsString();
String[] split = resp.split(",");
csvCoordinates = split[2]+","+split[3];
}
} catch (HttpException e) {
//
} catch (IOException e) {
//
}
return csvCoordinates;
}
/**
* Send the registration data now. If the user configured nothing to send,
* nothing will be sent.
*/
public void sendRegistrationData() {
// Do it optimistic and try to generate the XML message. If the message
// doesn't contain anything, the user does not want to register this
// instance
String registrationData = getRegistrationPropertiesMessage(null);
String registrationKey = persitedProperties.getStringPropertyValue(CONF_SECRETKEY, false);
if (StringHelper.containsNonWhitespace(registrationData)) {
// only send when there is something to send
HttpClient client = HttpClientFactory.getHttpClientInstance();
client.getParams().setParameter("http.useragent", "OLAT Registration Agent ; " + VERSION);
String url = REGISTRATION_SERVER+persitedProperties.getStringPropertyValue(CONF_KEY_IDENTIFYER, false)+"/";
logInfo("URL:"+url, null);
PutMethod method = new PutMethod(url);
if(registrationKey != null){
//updating
method.setRequestHeader("Authorization",registrationKey);
logInfo("Authorization: "+registrationKey,null);
}else{
logInfo("Authorization: NONE",null);
}
method.setRequestHeader("Content-Type", "application/xml; charset=utf-8");
try {
method.setRequestEntity(new StringRequestEntity(registrationData, "application/xml", "UTF8"));
client.executeMethod(method);
int status = method.getStatusCode();
if (status == HttpStatus.SC_NOT_MODIFIED || status == HttpStatus.SC_OK) {
logInfo("Successfully registered OLAT installation on olat.org server, thank you for your support!", null);
registrationKey = method.getResponseBodyAsString();
persitedProperties.setStringProperty(CONF_SECRETKEY, registrationKey, false);
persitedProperties.savePropertiesAndFireChangedEvent();
} else if (method.getStatusCode() == HttpStatus.SC_NOT_FOUND) {
logError("File could be created not on registration server::" + method.getStatusLine().toString(), null);
} else if(method.getStatusCode() == HttpStatus.SC_NO_CONTENT){
logInfo(method.getResponseBodyAsString(), method.getStatusText());
}
else {
logError("Unexpected HTTP Status::" + method.getStatusLine().toString() + " during registration call", null);
}
} catch (Exception e) {
logError("Unexpected exception during registration call", e);
}
} else {
logWarn(
"****************************************************************************************************************************************************************************",
null);
logWarn(
"* This OLAT installation is not registered. Please, help us with your statistical data and register your installation under Adminisration - Systemregistration. THANK YOU! *",
null);
logWarn(
"****************************************************************************************************************************************************************************",
null);
}
}
String getRegistrationPropertiesMessage(Properties tempConfiguration) {
Properties msgProperties = new Properties();
if (tempConfiguration == null) {
// Create temp properties from persisted properties
tempConfiguration = persitedProperties.createPropertiesFromPersistedProperties();
}
boolean sendStats = Boolean.parseBoolean(tempConfiguration.getProperty(CONF_KEY_SEND_STATISTICS));
boolean website = Boolean.parseBoolean(tempConfiguration.getProperty(CONF_KEY_PUBLISH_WEBSITE));
boolean notify = Boolean.parseBoolean(tempConfiguration.getProperty(CONF_KEY_NOTIFY_RELEASES));
if (sendStats || website || notify) {
msgProperties = tempConfiguration;
msgProperties.setProperty("RegistrationVersion", "1.0");
if (sendStats) {
// OLAT version
Version version = (Version) CoreSpringFactory.getBean("org.olat.core.Version");
msgProperties.setProperty("olatAppName", version.getApplicationName());
msgProperties.setProperty("olatVersion", version.getFullVersion());
// System config
msgProperties.setProperty("configInstantMessagingEnabled", String.valueOf(InstantMessagingModule.isEnabled()));
msgProperties.setProperty("configLanguages", I18nModule.getEnabledLanguageKeys().toString());
msgProperties.setProperty("configClusterEnabled", String.valueOf(CoordinatorManager.getCoordinator().isClusterMode()));
msgProperties.setProperty("configDebugginEnabled", String.valueOf(Settings.isDebuging()));
// Course counts
RepositoryManager repoMgr = RepositoryManager.getInstance();
int allCourses = repoMgr.countByTypeLimitAccess(CourseModule.ORES_TYPE_COURSE, RepositoryEntry.ACC_OWNERS);
int publishedCourses = repoMgr.countByTypeLimitAccess(CourseModule.ORES_TYPE_COURSE, RepositoryEntry.ACC_USERS);
msgProperties.setProperty("courseCountAll", String.valueOf(allCourses));
msgProperties.setProperty("courseCountPublished", String.valueOf(publishedCourses));
// User counts
Manager secMgr = ManagerFactory.getManager();
SecurityGroup olatuserGroup = secMgr.findSecurityGroupByName(Constants.GROUP_OLATUSERS);
int users = secMgr.countIdentitiesOfSecurityGroup(olatuserGroup);
int disabled = secMgr.getIdentitiesByPowerSearch(null, null, true, null, null, null, null, null, Identity.STATUS_LOGIN_DENIED)
.size();
msgProperties.setProperty("usersEnabled", String.valueOf(users - disabled));
PermissionOnResourceable[] permissions = { new PermissionOnResourceable(Constants.PERMISSION_HASROLE, Constants.ORESOURCE_AUTHOR) };
List<Identity> authorsList = secMgr.getIdentitiesByPowerSearch(null, null, true, null, permissions, null, null, null, null);
int authors = authorsList.size();
msgProperties.setProperty("usersAuthors", String.valueOf(authors));
// Activity
Calendar lastLoginLimit = Calendar.getInstance();
lastLoginLimit.add(Calendar.DAY_OF_YEAR, -6); // -1 - 6 = -7 for last
// week
msgProperties.setProperty("activeUsersLastWeek", String.valueOf(secMgr.countUniqueUserLoginsSince(lastLoginLimit.getTime())));
lastLoginLimit.add(Calendar.MONTH, -1);
msgProperties.setProperty("activeUsersLastMonth", String.valueOf(secMgr.countUniqueUserLoginsSince(lastLoginLimit.getTime())));
// Groups
BGContextManager groupMgr = BGContextManagerImpl.getInstance();
int buddyGroups = groupMgr.countGroupsOfType(BusinessGroup.TYPE_BUDDYGROUP);
msgProperties.setProperty("groupCountBuddyGroups", String.valueOf(buddyGroups));
int learningGroups = groupMgr.countGroupsOfType(BusinessGroup.TYPE_LEARNINGROUP);
msgProperties.setProperty("groupCountLearningGroups", String.valueOf(learningGroups));
int rightGroups = groupMgr.countGroupsOfType(BusinessGroup.TYPE_RIGHTGROUP);
msgProperties.setProperty("groupCountRightGroups", String.valueOf(rightGroups));
}
if (website) {
// URL
msgProperties.setProperty("websiteURL", Settings.getServerContextPathURI());
// Description
String desc = tempConfiguration.getProperty(CONF_KEY_WEBSITE_DESCRIPTION);
msgProperties.setProperty("websiteDescription", desc);
}
if (notify) {
// Email
String email = tempConfiguration.getProperty(CONF_KEY_EMAIL);
msgProperties.setProperty("email", email);
}
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
msgProperties.storeToXML(baos, "OLAT Registration Data, since 6.1.1 Release");
} catch (IOException e) {
throw new OLATRuntimeException("OLAT Registration failed",e);
}
String retVal = null;
try {
retVal = baos.toString("UTF8");
} catch (UnsupportedEncodingException e) {
throw new OLATRuntimeException("OLAT Registration failed",e);
}
return retVal;
}
/**
* Method to initialize the registration submission scheduler. The scheduler
* normally runs once a week and submitts the most current data.
*/
void setupRegistrationBackgroundThread() {
// Only run scheduler on first cluster node
// This is accomplished by the SystemRegistrationJobStarter which is configured and ensured to run only once in a cluster from within
// the olatextconfig.xml. This Job uses this method to setup the cronjob defined with the cronexpressioin from the properties.
//
// Don't run in jUnit mode
if (Settings.isJUnitTest()) return;
// create a crontrigger inside because cron expression is random generated -> this can not be done by config? REVIEW:gs:
Scheduler scheduler = (Scheduler) CoreSpringFactory.getBean("schedulerFactoryBean");
String cronExpression = "ERROR";
try {
// Create job with cron trigger configuration
JobDetail jobDetail = new JobDetail(SCHEDULER_NAME, Scheduler.DEFAULT_GROUP, SystemRegistrationJob.class);
CronTrigger trigger = new CronTrigger();
trigger.setName("system_registration_trigger");
cronExpression = persitedProperties.getStringPropertyValue(CONF_KEY_REGISTRATION_CRON, true);
if (!CronExpression.isValidExpression(cronExpression)) {
cronExpression = createCronTriggerExpression();
persitedProperties.setStringPropertyDefault(CONF_KEY_REGISTRATION_CRON, cronExpression);
}
// Use this cron expression for debugging, tries to send data every minute
//trigger.setCronExpression("0 * * * * ?");
trigger.setCronExpression(cronExpression);
// Schedule job now
scheduler.scheduleJob(jobDetail, trigger);
} catch (ParseException e) {
logError("Illegal cron expression for scheduling translation status generator", e);
} catch (SchedulerException e) {
logError("Can not start translation status generator scheduler", e);
}
logInfo("Registration background job successfully started: "+cronExpression, null);
}
/**
* @see org.olat.core.util.event.GenericEventListener#event(org.olat.core.gui.control.Event)
*/
public void event(Event event) {
if (event instanceof PersistedPropertiesChangedEvent) {
initConfiguration();
}
}
}