Add possibility to send data usage to LibrePlan server.

(cherry picked from commit acb6a2f with some changes!)
This commit is contained in:
Vova Perebykivskiy 2016-02-17 11:52:19 +02:00 committed by Tester
parent 9873d377d8
commit b071eca84d
9 changed files with 261 additions and 43 deletions

2
.gitignore vendored
View file

@ -10,7 +10,7 @@ ganttzk/target
.project
.settings/
# ignore IDEA configuration files
# ignore Intellij IDEA configuration files
.idea
*.iml

View file

@ -36,6 +36,7 @@ import org.apache.commons.logging.LogFactory;
* published. It checks the last version against a URL.
*
* @author Susana Montes Pedreira <smontes@wirelessgalicia.com>
* @author Vova Perebykivskiy <vova@libreplan-enterprise.com>
*/
public class VersionInformation {
@ -118,14 +119,12 @@ public class VersionInformation {
/**
* Returns true if a new version of the project is published.
*
* @param allowToGatherUsageStatsEnabled
* @param isCheckNewVersionEnabled
* If true LibrePlan developers will process the requests to check
* the new versions to generate usages statistics
*/
public static boolean isNewVersionAvailable(
boolean allowToGatherUsageStatsEnabled) {
return singleton
.checkIsNewVersionAvailable(allowToGatherUsageStatsEnabled);
public static boolean isNewVersionAvailable(boolean isCheckNewVersionEnabled) {
return singleton.checkIsNewVersionAvailable(isCheckNewVersionEnabled);
}
/**
@ -133,13 +132,12 @@ public class VersionInformation {
* Otherwise, during one day it returns the cached value. And it checks it
* again after that time.
*/
private boolean checkIsNewVersionAvailable(
boolean allowToGatherUsageStatsEnabled) {
if (!newVersionCached) {
private boolean checkIsNewVersionAvailable(boolean isCheckNewVersionEnabled) {
if ( !newVersionCached ) {
long oneDayLater = lastVersionCachedDate.getTime()
+ DELAY_TO_CHECK_URL;
if (oneDayLater < new Date().getTime()) {
loadNewVersionFromURL(allowToGatherUsageStatsEnabled);
if ( oneDayLater < new Date().getTime() ) {
loadNewVersionFromURL(isCheckNewVersionEnabled);
}
}
return newVersionCached;

View file

@ -37,6 +37,7 @@ import org.libreplan.business.costcategories.entities.TypeOfWorkHours;
* @author Cristina Alvarino Perez <cristina.alvarino@comtecsf.es>
* @author Ignacio Diaz Teijido <ignacio.diaz@comtecsf.es>
* @author Susana Montes Pedreira <smontes@wirelessgalicia.com>
* @author Vova Perebykivskiy <vova@libreplan-enterprise.com>
*/
public class Configuration extends BaseEntity {
@ -102,7 +103,7 @@ public class Configuration extends BaseEntity {
private Boolean checkNewVersionEnabled = true;
private Boolean allowToGatherUsageStatsEnabled = false;
private Boolean allowToGatherUsageStatsEnabled = true;
private Boolean generateCodeForExpenseSheets = true;

View file

@ -182,6 +182,7 @@ public class UserDAO extends GenericDAOHibernate<User, Long>
}
@Override
@Transactional(readOnly = true)
public Number getRowCount() {
return (Number) getSession().createCriteria(User.class).setProjection(Projections.rowCount()).uniqueResult();
}

View file

@ -0,0 +1 @@
statsPage http://stats.libreplan-enterprise.com/

View file

@ -31,6 +31,8 @@ import java.util.ArrayList;
import java.util.Set;
import java.util.Map;
import java.util.Collections;
import java.util.Comparator;
import java.util.ConcurrentModificationException;
import java.util.HashSet;
import java.util.Arrays;
@ -49,6 +51,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.cxf.jaxrs.client.WebClient;
import org.libreplan.business.calendars.entities.BaseCalendar;
import org.libreplan.business.common.daos.IConfigurationDAO;
import org.libreplan.business.common.entities.Configuration;
import org.libreplan.business.common.entities.Connector;
import org.libreplan.business.common.entities.ConnectorProperty;
@ -61,10 +64,12 @@ import org.libreplan.business.common.entities.PredefinedConnectors;
import org.libreplan.business.common.entities.ProgressType;
import org.libreplan.business.common.exceptions.ValidationException;
import org.libreplan.business.costcategories.entities.TypeOfWorkHours;
import org.libreplan.business.users.daos.IUserDAO;
import org.libreplan.business.users.entities.UserRole;
import org.libreplan.importers.JiraRESTClient;
import org.libreplan.importers.TimSoapClient;
import org.libreplan.web.common.components.bandboxsearch.BandboxSearch;
import org.libreplan.web.orders.IOrderModel;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.support.DefaultDirObjectFactory;
@ -125,6 +130,12 @@ public class ConfigurationController extends GenericForwardComposer {
private IConfigurationModel configurationModel;
private IConfigurationDAO configurationDAO;
private IUserDAO userDAO;
private IOrderModel orderModel;
private IMessagesForUser messages;
private Component messagesContainer;
@ -157,6 +168,8 @@ public class ConfigurationController extends GenericForwardComposer {
private Textbox emailSenderTextbox;
private boolean isGatheredStatsAlreadySent = false;
@Override
public void doAfterCompose(Component comp) throws Exception {
super.doAfterCompose(comp);
@ -242,24 +255,34 @@ public class ConfigurationController extends GenericForwardComposer {
public void save() throws InterruptedException {
if ( getSelectedConnector() != null && getSelectedConnector().getName().equals("E-mail") &&
isEmailFieldsValid() == false ) {
if (getSelectedConnector() != null && getSelectedConnector().getName().equals("E-mail") &&
isEmailFieldsValid() == false) {
messages.showMessage(Level.ERROR, _("Check all fields"));
} else {
ConstraintChecker.isValid(configurationWindow);
if (checkValidEntitySequenceRows()) {
try {
configurationModel.confirm();
configurationModel.init();
messages.showMessage(Level.INFO, _("Changes saved"));
if (getSelectedConnector() != null
&& !configurationModel
.scheduleOrUnscheduleJobs(getSelectedConnector())) {
messages.showMessage(
Level.ERROR,
_("Scheduling or unscheduling of jobs for this connector is not completed"));
}
ConstraintChecker.isValid(configurationWindow);
if (checkValidEntitySequenceRows()) {
try {
configurationModel.confirm();
configurationModel.init();
messages.showMessage(Level.INFO, _("Changes saved"));
// Send data to server
if (!isGatheredStatsAlreadySent && configurationDAO.getConfigurationWithReadOnlyTransaction()
.isAllowToGatherUsageStatsEnabled()) {
GatheredUsageStats gatheredUsageStats = new GatheredUsageStats();
gatheredUsageStats.setupNotAutowiredClasses(userDAO, orderModel);
gatheredUsageStats.sendGatheredUsageStatsToServer();
isGatheredStatsAlreadySent = true;
}
if (getSelectedConnector() != null
&& !configurationModel
.scheduleOrUnscheduleJobs(getSelectedConnector())) {
messages.showMessage(
Level.ERROR,
_("Scheduling or unscheduling of jobs for this connector is not completed"));
}
reloadWindow();
reloadEntitySequences();
reloadConnectors();
@ -274,7 +297,6 @@ public class ConfigurationController extends GenericForwardComposer {
}
}
}
}
public void cancel() throws InterruptedException {
@ -1186,7 +1208,6 @@ public class ConfigurationController extends GenericForwardComposer {
private void appendValueTextbox(Row row,
final ConnectorProperty property) {
final Textbox textbox = new Textbox();
textbox.setWidth("400px");
textbox.setConstraint(checkPropertyValue(property));

View file

@ -0,0 +1,185 @@
package org.libreplan.web.common;
import org.libreplan.business.common.VersionInformation;
import org.libreplan.business.users.daos.IUserDAO;
import org.libreplan.web.orders.IOrderModel;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetails;
import org.zkoss.json.JSONObject;
import org.zkoss.zk.ui.Execution;
import org.zkoss.zk.ui.Executions;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Properties;
/**
* Class represents all data that will be sent to server.
*
* If you want to use it, just create a new object and set 2 private variables.
* All needed data will be already in object.
*
* Created by
* @author Vova Perebykivskiy <vova@libreplan-enterprise.com>
* on 02/08/2016.
*/
public class GatheredUsageStats {
private IUserDAO userDAO;
private IOrderModel orderModel;
// Version of this statistics implementation
private int jsonObjectVersion = 1;
// Unique system identifier (MD5 - ip + hostname)
private String id;
// Version of LibrePlan
private String version = VersionInformation.getVersion();
// Number of users in application
private Number users;
// Number of projects in application
private int projects;
private Number getUserRows(){
return userDAO.getRowCount();
}
private String generateID(){
// Make hash of ip + hostname
WebAuthenticationDetails details = (WebAuthenticationDetails) SecurityContextHolder.getContext()
.getAuthentication().getDetails();
String ip = details.getRemoteAddress();
Execution execution = Executions.getCurrent();
String hostname = execution.getServerName();
String message = ip + hostname;
byte[] encoded = null;
StringBuffer sb = null;
try {
byte[] bytesOfMessage = message.getBytes("UTF-8");
MessageDigest md5 = MessageDigest.getInstance("MD5");
encoded = md5.digest(bytesOfMessage);
// Convert bytes to hex format
sb = new StringBuffer();
for (int i = 0; i < encoded.length; i++) sb.append(Integer.toString((encoded[i] & 0xff) + 0x100, 16)
.substring(1));
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return sb.toString();
}
// It needed because i do not need to call default constructor on Autowiring
private void myConstructor(){
setId(generateID());
setUsers(getUserRows());
setProjects(orderModel.getOrders().size());
}
public void sendGatheredUsageStatsToServer(){
JSONObject json = new JSONObject();
json.put("json-version", jsonObjectVersion);
json.put("id", id);
json.put("version", version);
json.put("users", users);
json.put("projects", projects);
HttpURLConnection connection = null;
Properties properties = new Properties();
InputStream inputStream = null;
try {
String filename = "libreplan.properties";
inputStream = GatheredUsageStats.class.getClassLoader().getResourceAsStream(filename);
properties.load(inputStream);
URL url = new URL(properties.getProperty("statsPage"));
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/x-www-urlencoded");
connection.setRequestProperty("Content-Language", "en-GB");
connection.setRequestProperty("Content-Length", Integer.toString(json.toJSONString().getBytes().length));
connection.setUseCaches(false);
connection.setDoInput(true);
connection.setDoOutput(true);
// If the connection lasts > 2 sec throw Exception
connection.setConnectTimeout(2000);
// Send request
DataOutputStream dataOutputStream = new DataOutputStream(connection.getOutputStream());
dataOutputStream.writeBytes(json.toJSONString());
dataOutputStream.flush();
dataOutputStream.close();
// No needed code, but it is not working without id
connection.getInputStream();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
finally {
if ( connection != null ) {
connection.disconnect();
}
}
}
public void setupNotAutowiredClasses(IUserDAO userDAO, IOrderModel orderModel){
this.userDAO = userDAO;
this.orderModel = orderModel;
myConstructor();
}
public int getJsonObjectVersion() {
return jsonObjectVersion;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getVersion() {
return version;
}
public Number getUsers() {
return users;
}
public void setUsers(Number users) {
this.users = users;
}
public int getProjects() {
return projects;
}
public void setProjects(int projects) {
this.projects = projects;
}
}

View file

@ -72,7 +72,7 @@ public class TemplateController extends GenericForwardComposer {
if (templateModel.isScenariosVisible()) {
window = (Window) comp.getFellow("changeScenarioWindow");
windowMessages = new MessagesForUser(window
.getFellow("messagesContainer"));
.getFellow("messagesContainer"));
}
}
@ -219,12 +219,11 @@ public class TemplateController extends GenericForwardComposer {
}
public boolean isNewVersionAvailable() {
if (!templateModel.isCheckNewVersionEnabled()) {
if ( !templateModel.isCheckNewVersionEnabled() ) {
return false;
}
return VersionInformation.isNewVersionAvailable(templateModel
.isAllowToGatherUsageStatsEnabled());
return VersionInformation.isNewVersionAvailable(templateModel.isCheckNewVersionEnabled());
}
public String getUsername() {

View file

@ -35,8 +35,10 @@ import org.libreplan.business.orders.entities.OrderElement;
import org.libreplan.business.planner.entities.TaskElement;
import org.libreplan.business.resources.daos.IResourcesSearcher;
import org.libreplan.business.templates.entities.OrderTemplate;
import org.libreplan.business.users.daos.IUserDAO;
import org.libreplan.business.users.entities.UserRole;
import org.libreplan.web.common.ConfirmCloseUtil;
import org.libreplan.web.common.GatheredUsageStats;
import org.libreplan.web.common.entrypoints.EntryPointsHandler;
import org.libreplan.web.common.entrypoints.URLHandlerRegistry;
import org.libreplan.web.dashboard.DashboardController;
@ -44,6 +46,7 @@ import org.libreplan.web.dashboard.DashboardControllerGlobal;
import org.libreplan.web.limitingresources.LimitingResourcesController;
import org.libreplan.web.logs.LogsController;
import org.libreplan.web.montecarlo.MonteCarloController;
import org.libreplan.web.orders.IOrderModel;
import org.libreplan.web.orders.OrderCRUDController;
import org.libreplan.web.planner.allocation.AdvancedAllocationController.IBack;
import org.libreplan.web.planner.company.CompanyPlanningController;
@ -76,18 +79,16 @@ import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.util.Composer;
import javax.swing.*;
/**
* Creates and handles several tabs
*
* @author Óscar González Fernández <ogonzalez@igalia.com>
* @author Lorenzo Tilve Álvaro <ltilve@igalia.com>
* @author Vova Perebykivskiy <vova@libreplan-enterprise.com>
*/
@Component
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class
MultipleTabsPlannerController implements Composer,
public class MultipleTabsPlannerController implements Composer,
IGlobalViewEntryPoints {
public static String WELCOME_URL = "-- no URL provided --";
@ -219,6 +220,15 @@ public class
@Autowired
private URLHandlerRegistry registry;
// Cannot Autowire it in GatheredUsageStats class
@Autowired
private IUserDAO userDAO;
@Autowired
private IOrderModel orderModel;
private boolean isGatheredStatsAlreadySent = false;
private TabsConfiguration buildTabsConfiguration(final Desktop desktop) {
Map<String, String[]> parameters = getURLQueryParametersMap();
@ -266,7 +276,7 @@ public class
@Override
public void goToTaskResourceAllocation(Order order,
TaskElement task) {
TaskElement task) {
orderPlanningController.setShowedTask(task);
orderPlanningController.setCurrentControllerToShow(orderPlanningController.getEditTaskController());
getTabsRegistry()
@ -499,6 +509,13 @@ public class
}
}
if ( !isGatheredStatsAlreadySent && configurationDAO.getConfiguration().isAllowToGatherUsageStatsEnabled() ){
GatheredUsageStats gatheredUsageStats = new GatheredUsageStats();
gatheredUsageStats.setupNotAutowiredClasses(userDAO, orderModel);
gatheredUsageStats.sendGatheredUsageStatsToServer();
isGatheredStatsAlreadySent = true;
}
}
private TabsRegistry getTabsRegistry() {
@ -516,11 +533,6 @@ public class
getTabsRegistry().show(resourceLoadTab);
}
/*@Override
public void goToLogs() {
getTabsRegistry().show(logsTab);
}*/
@Override
public void goToCompanyLimitingResources() {
getTabsRegistry().show(limitingResourcesTab);