Java tutorial
/** * 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.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.BaseSecurity; import org.olat.basesecurity.BaseSecurityManager; 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.configuration.Destroyable; import org.olat.core.configuration.Initializable; 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.event.FrameworkStartedEvent; import org.olat.core.util.event.FrameworkStartupEventChannel; 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, Initializable, Destroyable { private static final String POST_PARAMETER_NAME = "registrationData"; private static SystemRegistrationManager INSTANCE; // 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 PersistedProperties persitedProperties; private final Scheduler scheduler; private final String clusterMode; private final DB database; 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, Zrich", 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"; /** * [used by spring] Use getInstance(), this is a singleton */ private SystemRegistrationManager(final Scheduler scheduler, final String clusterMode, final DB database) { this.scheduler = scheduler; INSTANCE = this; this.clusterMode = clusterMode; this.database = database; FrameworkStartupEventChannel.registerForStartupEvent(this); } /** * [used by spring] * * @param persitedProperties */ public void setPersitedProperties(final PersistedProperties persitedProperties) { this.persitedProperties = persitedProperties; } /** * Call this to shutdown the cron scheduler and remove cluster event listeners from the PersistedProperties infrastructure */ @Override public void destroy() { // remove properties if (persitedProperties != null) { persitedProperties.destroy(); } // Stop registration job final Scheduler scheduler = (Scheduler) CoreSpringFactory.getBean("schedulerFactoryBean"); try { scheduler.deleteJob(SCHEDULER_NAME, Scheduler.DEFAULT_GROUP); } catch (final SchedulerException e) { logError("Could not shut down job::" + SCHEDULER_NAME, e); } } /** * Initialize the configuration */ @Override public void init() { // TODO why does this get set with each start? 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) { final 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) { final 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); } catch (final 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 final int min = RandomUtils.nextInt(59); final int hour = RandomUtils.nextInt(23); final int day = RandomUtils.nextInt(6); final 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; } /** * [used by spring] * * @param persistedProperties */ public void setPersistedProperties(final PersistedProperties persistedProperties) { this.persitedProperties = persistedProperties; } /** * @return The persisted configuration */ PersistedProperties getRegistrationConfiguration() { return persitedProperties; } String getLocationCoordinates(final String textLocation) { String csvCoordinates = null; if (textLocation == null || textLocation.length() == 0) { return null; } final HttpClient client = HttpClientFactory.getHttpClientInstance(); final String url = "http://maps.google.com/maps/geo"; final 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"); final GetMethod getCall = new GetMethod(url); getCall.setQueryString(nvps); try { client.executeMethod(getCall); String resp = null; if (getCall.getStatusCode() == 200) { resp = getCall.getResponseBodyAsString(); final String[] split = resp.split(","); csvCoordinates = split[2] + "," + split[3]; } } catch (final HttpException e) { // } catch (final 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 final String registrationData = getRegistrationPropertiesMessage(null); String registrationKey = persitedProperties.getStringPropertyValue(CONF_SECRETKEY, false); if (StringHelper.containsNonWhitespace(registrationData)) { // only send when there is something to send final HttpClient client = HttpClientFactory.getHttpClientInstance(); client.getParams().setParameter("http.useragent", "OLAT Registration Agent ; " + VERSION); final String url = REGISTRATION_SERVER + persitedProperties.getStringPropertyValue(CONF_KEY_IDENTIFYER, false) + "/"; logInfo("URL:" + url, null); final PutMethod method = new PutMethod(url); if (registrationKey != null) { // updating method.setRequestHeader("Authorization", registrationKey); if (isLogDebugEnabled()) { logDebug("Authorization: " + registrationKey, null); } else { logDebug("Authorization: EXISTS", 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); final 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 (final 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(); } final boolean website = Boolean.parseBoolean(tempConfiguration.getProperty(CONF_KEY_PUBLISH_WEBSITE)); final boolean notify = Boolean.parseBoolean(tempConfiguration.getProperty(CONF_KEY_NOTIFY_RELEASES)); if (website || notify) { msgProperties = tempConfiguration; msgProperties.setProperty("RegistrationVersion", "1.0"); // OLAT version msgProperties.setProperty("olatAppName", Settings.getApplicationName()); msgProperties.setProperty("olatVersion", Settings.getFullVersionInfo()); // System config msgProperties.setProperty("configInstantMessagingEnabled", String.valueOf(InstantMessagingModule.isEnabled())); msgProperties.setProperty("configLanguages", I18nModule.getEnabledLanguageKeys().toString()); msgProperties.setProperty("configClusterEnabled", clusterMode); msgProperties.setProperty("configDebugginEnabled", String.valueOf(Settings.isDebuging())); // Course counts final RepositoryManager repoMgr = RepositoryManager.getInstance(); final int allCourses = repoMgr.countByTypeLimitAccess(CourseModule.ORES_TYPE_COURSE, RepositoryEntry.ACC_OWNERS); final 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 final BaseSecurity secMgr = BaseSecurityManager.getInstance(); final SecurityGroup olatuserGroup = secMgr.findSecurityGroupByName(Constants.GROUP_OLATUSERS); final int users = secMgr.countIdentitiesOfSecurityGroup(olatuserGroup); final int disabled = secMgr.getIdentitiesByPowerSearch(null, null, true, null, null, null, null, null, null, null, Identity.STATUS_LOGIN_DENIED).size(); msgProperties.setProperty("usersEnabled", String.valueOf(users - disabled)); final PermissionOnResourceable[] permissions = { new PermissionOnResourceable(Constants.PERMISSION_HASROLE, Constants.ORESOURCE_AUTHOR) }; final List<Identity> authorsList = secMgr.getIdentitiesByPowerSearch(null, null, true, null, permissions, null, null, null, null, null, null); final int authors = authorsList.size(); msgProperties.setProperty("usersAuthors", String.valueOf(authors)); // Activity final 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 final BGContextManager groupMgr = BGContextManagerImpl.getInstance(); final int buddyGroups = groupMgr.countGroupsOfType(BusinessGroup.TYPE_BUDDYGROUP); msgProperties.setProperty("groupCountBuddyGroups", String.valueOf(buddyGroups)); final int learningGroups = groupMgr.countGroupsOfType(BusinessGroup.TYPE_LEARNINGROUP); msgProperties.setProperty("groupCountLearningGroups", String.valueOf(learningGroups)); final int rightGroups = groupMgr.countGroupsOfType(BusinessGroup.TYPE_RIGHTGROUP); msgProperties.setProperty("groupCountRightGroups", String.valueOf(rightGroups)); if (website) { // URL msgProperties.setProperty("websiteURL", Settings.getServerContextPathURI()); // Description final String desc = tempConfiguration.getProperty(CONF_KEY_WEBSITE_DESCRIPTION); msgProperties.setProperty("websiteDescription", desc); } if (notify) { // Email final String email = tempConfiguration.getProperty(CONF_KEY_EMAIL); msgProperties.setProperty("email", email); } } final ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { msgProperties.storeToXML(baos, "OLAT Registration Data, since 6.1.1 Release"); } catch (final IOException e) { throw new OLATRuntimeException("OLAT Registration failed", e); } String retVal = null; try { retVal = baos.toString("UTF8"); } catch (final 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: String cronExpression = "ERROR"; try { // Create job with cron trigger configuration final JobDetail jobDetail = new JobDetail(SCHEDULER_NAME, Scheduler.DEFAULT_GROUP, SystemRegistrationJob.class); final 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 (final ParseException e) { logError("Illegal cron expression for system registration", e); } catch (final SchedulerException e) { logError("Can not start system registration scheduler", e); } logInfo("Registration background job successfully started: " + cronExpression, null); } /** * @see org.olat.core.util.event.GenericEventListener#event(org.olat.core.gui.control.Event) */ @Override public void event(final Event event) { if (event instanceof PersistedPropertiesChangedEvent) { init(); } else if (event instanceof FrameworkStartedEvent) { // trigger first execution of registration when framework is started boolean success = false; try { sendRegistrationData(); success = true; database.commitAndCloseSession(); } finally { if (!success) { database.rollbackAndCloseSession(); } } } } }