/**
* OLAT - Online Learning and Training<br>
* http://www.olat.org
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br>
* you may not use this file except in compliance with the License.<br>
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing,<br>
* software distributed under the License is distributed on an "AS IS" BASIS, <br>
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
* See the License for the specific language governing permissions and <br>
* limitations under the License.
* <p>
* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br>
* University of Zurich, Switzerland.
* <hr>
* <a href="http://www.openolat.org">
* OpenOLAT - Online Learning and Training</a><br>
* This file has been modified by the OpenOLAT community. Changes are licensed
* under the Apache 2.0 license as the original file.
*/
package org.olat.admin.registration;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.ws.rs.core.UriBuilder;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.math.RandomUtils;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.olat.basesecurity.BaseSecurity;
import org.olat.basesecurity.Constants;
import org.olat.basesecurity.PermissionOnResourceable;
import org.olat.basesecurity.SecurityGroup;
import org.olat.core.CoreSpringFactory;
import org.olat.core.commons.persistence.DB;
import org.olat.core.helpers.Settings;
import org.olat.core.id.Identity;
import org.olat.core.logging.OLog;
import org.olat.core.logging.Tracing;
import org.olat.core.util.StringHelper;
import org.olat.core.util.httpclient.HttpClientFactory;
import org.olat.core.util.i18n.I18nModule;
import org.olat.course.CourseModule;
import org.olat.group.BusinessGroupService;
import org.olat.group.model.SearchBusinessGroupParams;
import org.olat.instantMessaging.InstantMessagingModule;
import org.olat.repository.RepositoryEntry;
import org.olat.repository.RepositoryManager;
import org.quartz.CronTrigger;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
/**
* 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
*/
@Service
public class SystemRegistrationManager implements InitializingBean {
private static final OLog log = Tracing.createLoggerFor(SystemRegistrationManager.class);
private static final String SCHEDULER_NAME = "system.registration";
private static final String TRIGGER = "system_registration_trigger";
public static final String PRODUCT = "openolat";
@Value("${cluster.mode}")
private String clusterMode;
@Autowired
private DB database;
@Autowired
private Scheduler scheduler;
@Autowired
private RepositoryManager repositoryManager;
@Autowired
private BaseSecurity securityManager;
@Autowired
private BusinessGroupService businessGroupService;
@Autowired
private SystemRegistrationModule registrationModule;
private static final String REGISTRATION_SERVER = "http://registration.openolat.org/registration/restapi/registration/openolat";
//private static final String REGISTRATION_SERVER = "http://localhost:8083/registration/restapi/registration/openolat";
/**
* Initialize the configuration
*/
@Override
public void afterPropertiesSet() throws Exception {
setupRegistrationBackgroundThread();
}
/**
* 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) + 1;
String cronExpression = "0 " + min + " " + hour + " ? * "+ day;
return cronExpression;
}
public String getLocationCoordinates(String textLocation){
if (textLocation == null || textLocation.length()==0) {
return null;
}
String csvCoordinates = null;
CloseableHttpClient client = null;
try {
client = HttpClientFactory.getHttpClientInstance(true);
URIBuilder uriBuilder = new URIBuilder("http://maps.google.com/maps/geo");
List<NameValuePair> nvps = new ArrayList<NameValuePair>(5);
nvps.add(new BasicNameValuePair("q",textLocation));
nvps.add(new BasicNameValuePair("output","csv"));
nvps.add(new BasicNameValuePair("oe","utf8"));
nvps.add(new BasicNameValuePair("sensor","false"));
nvps.add(new BasicNameValuePair("key","ABQIAAAAq5BZJrKbG-xh--W4MrciXRQZTOqTGVCcmpRMgrUbtlJvJ3buAhSfG7H7hgE66BCW17_gLyhitMNP4A"));
uriBuilder.addParameters(nvps);
HttpGet getCall = new HttpGet(uriBuilder.build());
HttpResponse response = client.execute(getCall);
String resp = null;
if(response.getStatusLine().getStatusCode() == 200){
resp = EntityUtils.toString(response.getEntity());
String[] split = resp.split(",");
csvCoordinates = split[2]+","+split[3];
}
} catch (Exception e) {
//
} finally {
IOUtils.closeQuietly(client);
}
return csvCoordinates;
}
public void send() {
try {
scheduler.triggerJob(SCHEDULER_NAME, Scheduler.DEFAULT_GROUP);
} catch (SchedulerException e) {
log.error("", e);
}
}
/**
* Send the registration data now. If the user configured nothing to send,
* nothing will be sent.
*/
protected void sendRegistrationData() {
HttpPut method = null;
CloseableHttpClient client = null;
try {
// 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
Map<String,String> registrationData = getRegistrationPropertiesMessage();
// only send when there is something to send
UriBuilder builder = UriBuilder.fromUri(REGISTRATION_SERVER);
for(Map.Entry<String, String> entry:registrationData.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
if(StringHelper.containsNonWhitespace(value)) {
builder.queryParam(key, value);
}
}
builder.queryParam("instanceId", registrationModule.getInstanceIdentifier());
if(StringHelper.containsNonWhitespace(registrationModule.getSecretKey())) {
String secretKey = registrationModule.getSecretKey();
builder.queryParam("secretKey", secretKey);
}
builder.queryParam("product", PRODUCT);
client = HttpClientFactory.getHttpClientInstance(true);
String url = builder.build().toString();
method = new HttpPut(url);
HttpResponse response = client.execute(method);
int status = response.getStatusLine().getStatusCode();
if(status == HttpStatus.SC_CREATED) {
log.info("Successfully registered OLAT installation on openolat.org server, thank you for your support!", null);
String registrationKey = EntityUtils.toString(response.getEntity());
registrationModule.setSecretKey(registrationKey);
} else if (status == HttpStatus.SC_NOT_MODIFIED || status == HttpStatus.SC_OK || status == HttpStatus.SC_CREATED) {
log.info("Successfully registered OLAT installation on openolat.org server, thank you for your support!", null);
} else if (status == HttpStatus.SC_NOT_FOUND) {
log.error("Registration server not found: " + response.getStatusLine().toString(), null);
} else if(status == HttpStatus.SC_NO_CONTENT){
log.info(EntityUtils.toString(response.getEntity()), response.getStatusLine().toString());
} else {
log.error("Unexpected HTTP Status: " + response.getStatusLine().toString() + " during registration call", null);
}
} catch (Exception e) {
log.error("Unexpected exception during registration call", e);
} finally {
database.commitAndCloseSession();
if(method != null) {
method.releaseConnection();
}
IOUtils.closeQuietly(client);
}
}
public Map<String,String> getRegistrationPropertiesMessage() {
Map<String,String> msgProperties = new HashMap<String,String>();
boolean website = registrationModule.isPublishWebsite();
boolean notify = registrationModule.isNotifyReleases();
//OLAT version
msgProperties.put("appName", Settings.getApplicationName());
msgProperties.put("version", Settings.getFullVersionInfo());
//Location
msgProperties.put("location", registrationModule.getLocation());
msgProperties.put("locationCSV", registrationModule.getLocationCoordinates());
// System config
msgProperties.put("instantMessagingEnabled", String.valueOf(CoreSpringFactory.getImpl(InstantMessagingModule.class).isEnabled()));
msgProperties.put("enabledLanguages", I18nModule.getEnabledLanguageKeys().toString());
msgProperties.put("clusterEnabled", clusterMode);
msgProperties.put("debuggingEnabled", String.valueOf(Settings.isDebuging()));
// Course counts
int allCourses = repositoryManager.countByTypeLimitAccess(CourseModule.ORES_TYPE_COURSE, RepositoryEntry.ACC_OWNERS);
int publishedCourses = repositoryManager.countByTypeLimitAccess(CourseModule.ORES_TYPE_COURSE, RepositoryEntry.ACC_USERS);
msgProperties.put("courses", String.valueOf(allCourses));
msgProperties.put("coursesPublished", String.valueOf(publishedCourses));
// User counts
SecurityGroup olatuserGroup = securityManager.findSecurityGroupByName(Constants.GROUP_OLATUSERS);
int users = securityManager.countIdentitiesOfSecurityGroup(olatuserGroup);
long disabled = securityManager.countIdentitiesByPowerSearch(null, null, true, null, null, null, null, null, null, null, Identity.STATUS_LOGIN_DENIED);
msgProperties.put("usersEnabled", String.valueOf(users - disabled));
PermissionOnResourceable[] permissions = { new PermissionOnResourceable(Constants.PERMISSION_HASROLE, Constants.ORESOURCE_AUTHOR) };
long authors = securityManager.countIdentitiesByPowerSearch(null, null, true, null, permissions, null, null, null, null, null, null);
msgProperties.put("authors", String.valueOf(authors));
// Activity
Calendar lastLoginLimit = Calendar.getInstance();
lastLoginLimit.add(Calendar.DAY_OF_YEAR, -6); // -1 - 6 = -7 for last week
Long activeUsersLastWeek = securityManager.countUniqueUserLoginsSince(lastLoginLimit.getTime());
msgProperties.put("activeUsersLastWeek", String.valueOf(activeUsersLastWeek));
lastLoginLimit = Calendar.getInstance();
lastLoginLimit.add(Calendar.MONTH, -1);
Long activeUsersLastMonth = securityManager.countUniqueUserLoginsSince(lastLoginLimit.getTime());
msgProperties.put("activeUsersLastMonth", String.valueOf(activeUsersLastMonth));
// Groups
SearchBusinessGroupParams params = new SearchBusinessGroupParams();
int groups = businessGroupService.countBusinessGroups(params, null);
msgProperties.put("buddyGroups", String.valueOf(groups));
msgProperties.put("learningGroups", String.valueOf(groups));
msgProperties.put("rightGroups", String.valueOf(groups));
msgProperties.put("groups", String.valueOf(groups));
// URL
msgProperties.put("url", Settings.getServerContextPathURI());
msgProperties.put("publishWebsite", String.valueOf(website));
// Description
String desc = registrationModule.getWebsiteDescription();
msgProperties.put("description", desc);
if (notify) {
// Email
String email = registrationModule.getEmail();
msgProperties.put("email", email);
}
return msgProperties;
}
/**
* Method to initialize the registration submission scheduler. The scheduler
* normally runs once a week and submitts the most current data.
*/
public 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;
String cronExpression = createCronTriggerExpression();
try {
// Create job with cron trigger configuration
JobDetail jobDetail = new JobDetail(SCHEDULER_NAME, Scheduler.DEFAULT_GROUP, SystemRegistrationJob.class);
CronTrigger trigger = new CronTrigger();
trigger.setName(TRIGGER);
// 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) {
log.error("Illegal cron expression for system registration", e);
} catch (SchedulerException e) {
log.error("Can not start system registration scheduler", e);
}
log.info("Registration background job successfully started: "+cronExpression, null);
}
}