diff --git a/pom.xml b/pom.xml
index 94b30fcb65..804c0f5cd5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -96,6 +96,11 @@
alfresco-events
1.2.5
+
+ org.alfresco
+ alfresco-heartbeat-data-sender
+ 1.0.0
+
com.sun.mail
javax.mail
diff --git a/src/main/java/org/alfresco/heartbeat/HBBaseDataCollector.java b/src/main/java/org/alfresco/heartbeat/HBBaseDataCollector.java
new file mode 100644
index 0000000000..dbff6352ac
--- /dev/null
+++ b/src/main/java/org/alfresco/heartbeat/HBBaseDataCollector.java
@@ -0,0 +1,48 @@
+/*
+ * #%L
+ * Alfresco Repository
+ * %%
+ * Copyright (C) 2005 - 2017 Alfresco Software Limited
+ * %%
+ * This file is part of the Alfresco software.
+ * If the software was purchased under a paid Alfresco license, the terms of
+ * the paid license agreement will prevail. Otherwise, the software is
+ * provided under the following open source license terms:
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ * #L%
+ */
+package org.alfresco.heartbeat;
+
+import java.util.List;
+
+import org.alfresco.heartbeat.datasender.HBData;
+import org.alfresco.service.cmr.repository.HBDataCollectorService;
+
+public abstract class HBBaseDataCollector
+{
+ private HBDataCollectorService hbDataCollectorService;
+
+ public void register()
+ {
+ hbDataCollectorService.registerCollector(this);
+ }
+
+ public void setHbDataCollectorService(HBDataCollectorService hbDataCollectorService)
+ {
+ this.hbDataCollectorService = hbDataCollectorService;
+ }
+
+ public abstract List collectData();
+}
diff --git a/src/main/java/org/alfresco/heartbeat/HBDataCollectorServiceImpl.java b/src/main/java/org/alfresco/heartbeat/HBDataCollectorServiceImpl.java
new file mode 100644
index 0000000000..b131178eea
--- /dev/null
+++ b/src/main/java/org/alfresco/heartbeat/HBDataCollectorServiceImpl.java
@@ -0,0 +1,112 @@
+/*
+ * #%L
+ * Alfresco Repository
+ * %%
+ * Copyright (C) 2005 - 2017 Alfresco Software Limited
+ * %%
+ * This file is part of the Alfresco software.
+ * If the software was purchased under a paid Alfresco license, the terms of
+ * the paid license agreement will prevail. Otherwise, the software is
+ * provided under the following open source license terms:
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ * #L%
+ */
+package org.alfresco.heartbeat;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.alfresco.heartbeat.datasender.HBData;
+import org.alfresco.heartbeat.datasender.HBDataSenderService;
+import org.alfresco.service.cmr.repository.HBDataCollectorService;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+public class HBDataCollectorServiceImpl implements HBDataCollectorService
+{
+ /** The logger. */
+ private static final Log logger = LogFactory.getLog(HBDataCollectorServiceImpl.class);
+
+ /** List of collectors registered with this service */
+ private List collectors = new LinkedList<>();
+
+ /** The service responsible for sending the collected data */
+ private HBDataSenderService hbDataSenderService;
+
+ /** The default enable state */
+ private final boolean defaultHbState;
+
+ /**
+ *
+ * @param defaultHeartBeatState the default enabled state of heartbeat
+ *
+ */
+ public HBDataCollectorServiceImpl (boolean defaultHeartBeatState)
+ {
+ this.defaultHbState = defaultHeartBeatState;
+ }
+
+ public void setHbDataSenderService(HBDataSenderService hbDataSenderService)
+ {
+ this.hbDataSenderService = hbDataSenderService;
+ }
+
+ /**
+ *
+ * Register data collector with this service.
+ * The registered collectors will be called to provide heartbeat data.
+ *
+ * @param collector collector to register
+ */
+ @Override
+ public void registerCollector(HBBaseDataCollector collector)
+ {
+ this.collectors.add(collector);
+ }
+
+ /**
+ * Collects and sends data for all registered collectors using the provided sender service.
+ */
+ @Override
+ public void collectAndSendData()
+ {
+ for (HBBaseDataCollector collector : collectors)
+ {
+ List data = collector.collectData();
+ try
+ {
+ hbDataSenderService.sendData(data);
+ }
+ catch (Exception e)
+ {
+ // Log exception
+ logger.error(e);
+ }
+ }
+ }
+
+ @Override
+ public boolean isEnabledByDefault()
+ {
+ return defaultHbState;
+ }
+
+ @Override
+ public synchronized void enabled(boolean enabled)
+ {
+ this.hbDataSenderService.enable(enabled);
+ }
+
+}
diff --git a/src/main/java/org/alfresco/heartbeat/HBDataSenderServiceFactory.java b/src/main/java/org/alfresco/heartbeat/HBDataSenderServiceFactory.java
new file mode 100644
index 0000000000..bae13bd5ec
--- /dev/null
+++ b/src/main/java/org/alfresco/heartbeat/HBDataSenderServiceFactory.java
@@ -0,0 +1,57 @@
+/*
+ * #%L
+ * Alfresco Repository
+ * %%
+ * Copyright (C) 2005 - 2016 Alfresco Software Limited
+ * %%
+ * This file is part of the Alfresco software.
+ * If the software was purchased under a paid Alfresco license, the terms of
+ * the paid license agreement will prevail. Otherwise, the software is
+ * provided under the following open source license terms:
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ * #L%
+ */
+package org.alfresco.heartbeat;
+
+import org.alfresco.heartbeat.datasender.HBDataSenderServiceBuilder;
+import org.alfresco.heartbeat.datasender.HBDataSenderService;
+
+/**
+ * @author Ancuta Morarasu
+ *
+ */
+public class HBDataSenderServiceFactory
+{
+ private String target;
+ private boolean heartbeatEnabled;
+
+ public void setTarget(String target)
+ {
+ this.target = target;
+ }
+
+ public void setHeartbeatEnabled(boolean heartbeatEnabled)
+ {
+ this.heartbeatEnabled = heartbeatEnabled;
+ }
+
+ public HBDataSenderService createInstance()
+ {
+ return HBDataSenderServiceBuilder.builder()
+ .withHeartbeatURL(target)
+ .enable(heartbeatEnabled)
+ .build();
+ }
+}
diff --git a/src/main/java/org/alfresco/heartbeat/HeartBeat.java b/src/main/java/org/alfresco/heartbeat/HeartBeat.java
new file mode 100644
index 0000000000..57dee13dcc
--- /dev/null
+++ b/src/main/java/org/alfresco/heartbeat/HeartBeat.java
@@ -0,0 +1,272 @@
+/*
+ * #%L
+ * Alfresco Repository
+ * %%
+ * Copyright (C) 2005 - 2016 Alfresco Software Limited
+ * %%
+ * This file is part of the Alfresco software.
+ * If the software was purchased under a paid Alfresco license, the terms of
+ * the paid license agreement will prevail. Otherwise, the software is
+ * provided under the following open source license terms:
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ * #L%
+ */
+package org.alfresco.heartbeat;
+
+import java.util.Date;
+
+import org.alfresco.service.cmr.repository.HBDataCollectorService;
+import org.alfresco.service.license.LicenseDescriptor;
+import org.alfresco.service.license.LicenseService;
+import org.alfresco.service.license.LicenseService.LicenseChangeHandler;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.quartz.Job;
+import org.quartz.JobDataMap;
+import org.quartz.JobDetail;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
+import org.quartz.SimpleTrigger;
+import org.quartz.Trigger;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.context.ApplicationContext;
+
+/**
+ * This class communicates some very basic repository statistics to Alfresco on a regular basis.
+ * The class is responsible for scheduling the HeartBeat job and reacting to licence change events.
+ *
+ * @author dward, eknizat
+ */
+public class HeartBeat implements LicenseChangeHandler
+{
+
+ /** The logger */
+ private static final Log logger = LogFactory.getLog(HeartBeat.class);
+
+ private LicenseService licenseService;
+
+ private Scheduler scheduler;
+
+ private boolean testMode = true;
+
+ private final String JOB_NAME = "heartbeat";
+
+ private HBDataCollectorService dataCollectorService;
+
+ /** Current enabled state */
+ private boolean enabled = false;
+
+ /**
+ * Initialises the heart beat service. Note that dependencies are intentionally 'pulled' rather than injected
+ * because we don't want these to be reconfigured.
+ *
+ * @param context
+ * the context
+ */
+ public HeartBeat(final ApplicationContext context)
+ {
+ this(context, false);
+ }
+
+ /**
+ * Initialises the heart beat service, potentially in test mode. Note that dependencies are intentionally 'pulled'
+ * rather than injected because we don't want these to be reconfigured.
+ *
+ * -@param context
+ * the context
+ * -@param testMode
+ * are we running in test mode? If so we send data to local port 9999 rather than an alfresco server. We
+ * also use a special test encryption certificate and ping on a more frequent basis.
+ */
+ public HeartBeat(final ApplicationContext context, final Boolean testMode)
+ {
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("Initialising HeartBeat");
+ }
+
+ this.dataCollectorService = (HBDataCollectorService) context.getBean("hbDataCollectorService");
+ this.scheduler = (Scheduler) context.getBean("schedulerFactory");
+
+ this.testMode = testMode;
+
+ this.enabled = dataCollectorService.isEnabledByDefault();
+
+ try
+ {
+ LicenseService licenseService = null;
+ try
+ {
+ licenseService = (LicenseService) context.getBean("licenseService");
+ licenseService.registerOnLicenseChange(this);
+ }
+ catch (NoSuchBeanDefinitionException e)
+ {
+ logger.error("licenseService not found", e);
+ }
+ this.licenseService = licenseService;
+
+ // We force the job to be scheduled regardless of the potential state of the licenses
+ scheduleJob();
+ }
+ catch (final RuntimeException e)
+ {
+ throw e;
+ }
+ catch (final Exception e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+
+ public synchronized boolean isEnabled()
+ {
+ return this.enabled;
+ }
+
+ /**
+ * Delegates data collection and sending to HBDataCollectorService.
+ *
+ */
+ public void collectAndSendData()
+ {
+ this.dataCollectorService.collectAndSendData();
+ }
+
+ /**
+ * Listens for license changes. If a license is change or removed, the heartbeat job is rescheduled.
+ */
+ public synchronized void onLicenseChange(LicenseDescriptor licenseDescriptor)
+ {
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("Update license called");
+ }
+
+ boolean newEnabled = !licenseDescriptor.isHeartBeatDisabled();
+
+ if (newEnabled != this.enabled)
+ {
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("State change of heartbeat");
+ }
+ this.enabled = newEnabled;
+ dataCollectorService.enabled(newEnabled);
+ try
+ {
+ scheduleJob();
+ }
+ catch (Exception e)
+ {
+ logger.error("Unable to schedule heart beat", e);
+ }
+ }
+ }
+
+ /**
+ * License load failure resets the heartbeat back to the default state
+ */
+ @Override
+ public synchronized void onLicenseFail()
+ {
+ boolean newEnabled = dataCollectorService.isEnabledByDefault();
+
+ if (newEnabled != this.enabled)
+ {
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("State change of heartbeat");
+ }
+ this.enabled = newEnabled;
+ dataCollectorService.enabled(newEnabled);
+ try
+ {
+ scheduleJob();
+ }
+ catch (Exception e)
+ {
+ logger.error("Unable to schedule heart beat", e);
+ }
+ }
+ }
+
+ /**
+ * Start or stop the hertbeat job depending on whether the heartbeat is enabled or not
+ * @throws SchedulerException
+ */
+ private synchronized void scheduleJob() throws SchedulerException
+ {
+ // Schedule the heart beat to run regularly
+ final String triggerName = JOB_NAME + "Trigger";
+ if(this.enabled)
+ {
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("heartbeat job scheduled");
+ }
+ final JobDetail jobDetail = new JobDetail(JOB_NAME, Scheduler.DEFAULT_GROUP, HeartBeatJob.class);
+ jobDetail.getJobDataMap().put("heartBeat", this);
+
+ // Ensure the job wasn't already scheduled in an earlier retry of this transaction
+ scheduler.unscheduleJob(triggerName, Scheduler.DEFAULT_GROUP);
+ final Trigger trigger = new SimpleTrigger(triggerName, Scheduler.DEFAULT_GROUP, new Date(), null,
+ //SimpleTrigger.REPEAT_INDEFINITELY, testMode ? 1000 : 4 * 60 * 60 * 1000);
+ SimpleTrigger.REPEAT_INDEFINITELY, testMode ? 1000 : 2 * 60 * 1000);
+ scheduler.scheduleJob(jobDetail, trigger);
+ }
+ else
+ {
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("heartbeat job unscheduled");
+ }
+ scheduler.unscheduleJob(triggerName, Scheduler.DEFAULT_GROUP);
+ }
+ }
+
+ /**
+ * The scheduler job responsible for triggering a heartbeat on a regular basis.
+ */
+ public static class HeartBeatJob implements Job
+ {
+ public void execute(final JobExecutionContext jobexecutioncontext) throws JobExecutionException
+ {
+ final JobDataMap dataMap = jobexecutioncontext.getJobDetail().getJobDataMap();
+ final HeartBeat heartBeat = (HeartBeat) dataMap.get("heartBeat");
+ try
+ {
+ heartBeat.collectAndSendData();
+ }
+ catch (final Exception e)
+ {
+ if (logger.isDebugEnabled())
+ {
+ // Verbose logging
+ HeartBeat.logger.debug("Heartbeat job failure", e);
+ }
+ else
+ {
+ // Heartbeat errors are non-fatal and will show as single line warnings
+ HeartBeat.logger.warn(e.toString());
+ throw new JobExecutionException(e);
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/org/alfresco/heartbeat/RepositoryDataCollector.java b/src/main/java/org/alfresco/heartbeat/RepositoryDataCollector.java
new file mode 100644
index 0000000000..4fc7e02d05
--- /dev/null
+++ b/src/main/java/org/alfresco/heartbeat/RepositoryDataCollector.java
@@ -0,0 +1,281 @@
+/*
+ * #%L
+ * Alfresco Repository
+ * %%
+ * Copyright (C) 2005 - 2017 Alfresco Software Limited
+ * %%
+ * This file is part of the Alfresco software.
+ * If the software was purchased under a paid Alfresco license, the terms of
+ * the paid license agreement will prevail. Otherwise, the software is
+ * provided under the following open source license terms:
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ * #L%
+ */
+package org.alfresco.heartbeat;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.security.GeneralSecurityException;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import org.alfresco.heartbeat.datasender.HBData;
+import org.alfresco.repo.descriptor.DescriptorDAO;
+import org.alfresco.repo.dictionary.CustomModelsInfo;
+import org.alfresco.repo.transaction.RetryingTransactionHelper;
+import org.alfresco.repo.usage.RepoUsageComponent;
+import org.alfresco.service.cmr.admin.RepoUsage;
+import org.alfresco.service.cmr.dictionary.CustomModelService;
+import org.alfresco.service.cmr.security.AuthorityService;
+import org.alfresco.service.cmr.security.AuthorityType;
+import org.alfresco.service.descriptor.Descriptor;
+import org.alfresco.service.transaction.TransactionService;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+public class RepositoryDataCollector extends HBBaseDataCollector
+{
+
+ /** The logger. */
+ private static final Log logger = LogFactory.getLog(RepositoryDataCollector.class);
+
+ /**
+ * The parameters that we expect to remain static throughout the lifetime of the repository. There is no need to
+ * continuously update these.
+ */
+ private Map staticParameters;
+
+ /** The transaction service. */
+ private TransactionService transactionService;
+
+ /** DAO for current repository descriptor. */
+ private DescriptorDAO currentRepoDescriptorDAO;
+
+ /** DAO for current descriptor. */
+ private DescriptorDAO serverDescriptorDAO;
+
+ /** The authority service. */
+ private AuthorityService authorityService;
+
+ private RepoUsageComponent repoUsageComponent;
+
+ /** Provides information about custom models */
+ private CustomModelService customModelService;
+
+ public void setCurrentRepoDescriptorDAO(DescriptorDAO currentRepoDescriptorDAO)
+ {
+ this.currentRepoDescriptorDAO = currentRepoDescriptorDAO;
+ }
+
+ public void setServerDescriptorDAO(DescriptorDAO serverDescriptorDAO)
+ {
+ this.serverDescriptorDAO = serverDescriptorDAO;
+ }
+
+ public void setAuthorityService(AuthorityService authorityService)
+ {
+ this.authorityService = authorityService;
+ }
+
+ public void setRepoUsageComponent(RepoUsageComponent repoUsageComponent)
+ {
+ this.repoUsageComponent = repoUsageComponent;
+ }
+
+ public void setTransactionService(TransactionService transactionService)
+ {
+ this.transactionService = transactionService;
+ }
+
+ public void setCustomModelService(CustomModelService customModelService)
+ {
+ this.customModelService = customModelService;
+ }
+
+ @Override
+ public List collectData()
+ {
+ List collectedData = new LinkedList<>();
+
+ RetryingTransactionHelper.RetryingTransactionCallback initCallback = new RetryingTransactionHelper.RetryingTransactionCallback()
+ {
+ @Override
+ public Void execute() throws Throwable
+ {
+ lazyInit();
+ return null;
+ }
+ };
+ transactionService.getRetryingTransactionHelper().doInTransaction(initCallback, true);
+
+ // collect repository info data
+ logger.debug("Preparing repository info data...");
+ Map infoValues = new HashMap();
+ infoValues.put("repoName", this.staticParameters.get("repoName"));
+ infoValues.put("edition", this.staticParameters.get("edition"));
+ infoValues.put("versionMajor", this.staticParameters.get("versionMajor"));
+ infoValues.put("versionMinor", this.staticParameters.get("versionMinor"));
+ infoValues.put("schema", this.staticParameters.get("schema"));
+ HBData infoData = new HBData(
+ this.currentRepoDescriptorDAO.getDescriptor().getId(),
+ "acs.repository.info",
+ "1.0",
+ new Date(),
+ infoValues);
+ collectedData.add(infoData);
+
+ // collect repository usage (system) data
+ logger.debug("Preparing repository usage (system) data...");
+ Runtime runtime = Runtime.getRuntime();
+ Map systemUsageValues = new HashMap();
+ systemUsageValues.put("memFree", runtime.freeMemory());
+ systemUsageValues.put("memMax", runtime.maxMemory());
+ systemUsageValues.put("memTotal", runtime.totalMemory());
+ HBData systemUsageData = new HBData(
+ this.currentRepoDescriptorDAO.getDescriptor().getId(),
+ "acs.repository.usage.system",
+ "1.0",
+ new Date(),
+ systemUsageValues);
+ collectedData.add(systemUsageData);
+
+ // collect repository usage (model) data
+ logger.debug("Preparing repository usage (model) data...");
+ final CustomModelsInfo customModelsInfo = transactionService.getRetryingTransactionHelper().doInTransaction(
+ new RetryingTransactionHelper.RetryingTransactionCallback()
+ {
+ public CustomModelsInfo execute()
+ {
+ return customModelService.getCustomModelsInfo();
+ }
+ }, true);
+
+ Map modelUsageValues = new HashMap();
+ modelUsageValues.put("numOfActiveModels", new Integer(customModelsInfo.getNumberOfActiveModels()));
+ modelUsageValues.put("numOfActiveTypes", new Integer(customModelsInfo.getNumberOfActiveTypes()));
+ modelUsageValues.put("numOfActiveAspects", new Integer(customModelsInfo.getNumberOfActiveAspects()));
+ HBData modelUsageData = new HBData(
+ this.currentRepoDescriptorDAO.getDescriptor().getId(),
+ "acs.repository.usage.model",
+ "1.0",
+ new Date(),
+ modelUsageValues);
+ collectedData.add(modelUsageData);
+
+ return collectedData;
+ }
+
+ /**
+ * Initializes static parameters on first invocation. Avoid doing it on construction due to bootstrap dependencies
+ * (e.g. patch service must have run)
+ *
+ * @throws GeneralSecurityException
+ * @throws IOException
+ */
+ private synchronized void lazyInit() throws GeneralSecurityException, IOException
+ {
+ if (this.staticParameters == null)
+ {
+ this.staticParameters = new TreeMap();
+
+ // Load up the static parameters
+ final String ip = getLocalIps();
+ this.staticParameters.put("ip", ip);
+ final String uid;
+ final Descriptor currentRepoDescriptor = this.currentRepoDescriptorDAO.getDescriptor();
+ if (currentRepoDescriptor != null)
+ {
+ uid = currentRepoDescriptor.getId();
+ this.staticParameters.put("uid", uid);
+ }
+ else
+ {
+ uid = "Unknown";
+ }
+
+ final Descriptor serverDescriptor = this.serverDescriptorDAO.getDescriptor();
+ this.staticParameters.put("repoName", serverDescriptor.getName());
+ this.staticParameters.put("edition", serverDescriptor.getEdition());
+ this.staticParameters.put("versionMajor", serverDescriptor.getVersionMajor());
+ this.staticParameters.put("versionMinor", serverDescriptor.getVersionMinor());
+ this.staticParameters.put("schema", new Integer(serverDescriptor.getSchema()));
+ this.staticParameters.put("numUsers", new Integer(this.authorityService.getAllAuthoritiesInZone(
+ AuthorityService.ZONE_APP_DEFAULT, AuthorityType.USER).size()));
+ this.staticParameters.put("numGroups", new Integer(this.authorityService.getAllAuthoritiesInZone(
+ AuthorityService.ZONE_APP_DEFAULT, AuthorityType.GROUP).size()));
+
+ if(repoUsageComponent != null)
+ {
+ RepoUsage usage = repoUsageComponent.getUsage();
+
+ if (usage.getUsers() != null)
+ {
+ this.staticParameters.put("licenseUsers", new Long((usage.getUsers())));
+ }
+ }
+
+ }
+ }
+
+ /**
+ * Attempts to get all the local IP addresses of this machine in order to distinguish it from other nodes in the
+ * same network. The machine may use a static IP address in conjunction with a loopback adapter (e.g. to support
+ * Oracle on Windows), so the IP of the default network interface may not be enough to uniquely identify this
+ * machine.
+ *
+ * @return the local IP addresses, separated by the '/' character
+ */
+ private String getLocalIps()
+ {
+ final StringBuilder ip = new StringBuilder(1024);
+ boolean first = true;
+ try
+ {
+ final Enumeration i = NetworkInterface.getNetworkInterfaces();
+ while (i.hasMoreElements())
+ {
+ final NetworkInterface n = i.nextElement();
+ final Enumeration j = n.getInetAddresses();
+ while (j.hasMoreElements())
+ {
+ InetAddress a = j.nextElement();
+ if (a.isLoopbackAddress())
+ {
+ continue;
+ }
+ if (first)
+ {
+ first = false;
+ }
+ else
+ {
+ ip.append('/');
+ }
+ ip.append(a.getHostAddress());
+ }
+ }
+ }
+ catch (final Exception e)
+ {
+ // Ignore
+ }
+ return first ? "127.0.0.1" : ip.toString();
+ }
+}
diff --git a/src/main/java/org/alfresco/repo/descriptor/DescriptorServiceImpl.java b/src/main/java/org/alfresco/repo/descriptor/DescriptorServiceImpl.java
index a928c23416..8e519835b0 100644
--- a/src/main/java/org/alfresco/repo/descriptor/DescriptorServiceImpl.java
+++ b/src/main/java/org/alfresco/repo/descriptor/DescriptorServiceImpl.java
@@ -1,28 +1,28 @@
-/*
- * #%L
- * Alfresco Repository
- * %%
- * Copyright (C) 2005 - 2016 Alfresco Software Limited
- * %%
- * This file is part of the Alfresco software.
- * If the software was purchased under a paid Alfresco license, the terms of
- * the paid license agreement will prevail. Otherwise, the software is
- * provided under the following open source license terms:
- *
- * Alfresco is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Alfresco is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with Alfresco. If not, see .
- * #L%
- */
+/*
+ * #%L
+ * Alfresco Repository
+ * %%
+ * Copyright (C) 2005 - 2016 Alfresco Software Limited
+ * %%
+ * This file is part of the Alfresco software.
+ * If the software was purchased under a paid Alfresco license, the terms of
+ * the paid license agreement will prevail. Otherwise, the software is
+ * provided under the following open source license terms:
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ * #L%
+ */
package org.alfresco.repo.descriptor;
import java.io.InputStream;
@@ -303,9 +303,9 @@ public class DescriptorServiceImpl extends AbstractLifecycleBean
((ConfigurableApplicationContext) applicationContext).getBeanFactory().registerSingleton(
"licenseService", licenseService);
}
-
+
// Load heart-beat special service (even if disabled at the moment)
- heartBeat = constructSpecialService("org.alfresco.enterprise.heartbeat.HeartBeat");
+ heartBeat = constructSpecialService("org.alfresco.heartbeat.HeartBeat");
// Now listen for future license changes
licenseService.registerOnLicenseChange(this);
diff --git a/src/main/java/org/alfresco/service/cmr/repository/HBDataCollectorService.java b/src/main/java/org/alfresco/service/cmr/repository/HBDataCollectorService.java
new file mode 100644
index 0000000000..95631c4b2f
--- /dev/null
+++ b/src/main/java/org/alfresco/service/cmr/repository/HBDataCollectorService.java
@@ -0,0 +1,41 @@
+/*
+ * #%L
+ * Alfresco Repository
+ * %%
+ * Copyright (C) 2005 - 2017 Alfresco Software Limited
+ * %%
+ * This file is part of the Alfresco software.
+ * If the software was purchased under a paid Alfresco license, the terms of
+ * the paid license agreement will prevail. Otherwise, the software is
+ * provided under the following open source license terms:
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ * #L%
+ */
+package org.alfresco.service.cmr.repository;
+
+import org.alfresco.heartbeat.HBBaseDataCollector;
+
+public interface HBDataCollectorService
+{
+ void registerCollector(HBBaseDataCollector collector);
+
+ void collectAndSendData();
+
+ void enabled(boolean enabled);
+
+ boolean isEnabledByDefault();
+
+
+}
diff --git a/src/main/resources/alfresco/application-context-core.xml b/src/main/resources/alfresco/application-context-core.xml
index ddac5f146c..259a181c03 100644
--- a/src/main/resources/alfresco/application-context-core.xml
+++ b/src/main/resources/alfresco/application-context-core.xml
@@ -52,4 +52,5 @@
+
diff --git a/src/main/resources/alfresco/application-context-highlevel.xml b/src/main/resources/alfresco/application-context-highlevel.xml
index 8273b7ee91..a949204a9c 100644
--- a/src/main/resources/alfresco/application-context-highlevel.xml
+++ b/src/main/resources/alfresco/application-context-highlevel.xml
@@ -43,4 +43,5 @@
+
diff --git a/src/main/resources/alfresco/bootstrap-context.xml b/src/main/resources/alfresco/bootstrap-context.xml
index 451d23ab3b..4ef5367571 100644
--- a/src/main/resources/alfresco/bootstrap-context.xml
+++ b/src/main/resources/alfresco/bootstrap-context.xml
@@ -269,7 +269,7 @@
-
+
diff --git a/src/main/resources/alfresco/heartbeat/heartbeat-context.xml b/src/main/resources/alfresco/heartbeat/heartbeat-context.xml
new file mode 100644
index 0000000000..9c96d66bd4
--- /dev/null
+++ b/src/main/resources/alfresco/heartbeat/heartbeat-context.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/alfresco/heartbeat/heartbeat-repo-collector-context.xml b/src/main/resources/alfresco/heartbeat/heartbeat-repo-collector-context.xml
new file mode 100644
index 0000000000..67ea957994
--- /dev/null
+++ b/src/main/resources/alfresco/heartbeat/heartbeat-repo-collector-context.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/alfresco/repository.properties b/src/main/resources/alfresco/repository.properties
index 1af3bf8546..e21f203bd9 100644
--- a/src/main/resources/alfresco/repository.properties
+++ b/src/main/resources/alfresco/repository.properties
@@ -1242,3 +1242,7 @@ system.email.sender.default=noreply@alfresco.com
# reset password workflow will expire in an hour
system.reset-password.endTimer=PT1H
system.reset-password.sendEmailAsynchronously=true
+
+# HeartBeat
+heartbeat.enabled=false
+heartbeat.target.url=heartbeat.alfresco.com
diff --git a/src/test/java/org/alfresco/AllRepositoryTestsCatalogue.java b/src/test/java/org/alfresco/AllRepositoryTestsCatalogue.java
index a0bcbea78f..fbf8ea4c49 100644
--- a/src/test/java/org/alfresco/AllRepositoryTestsCatalogue.java
+++ b/src/test/java/org/alfresco/AllRepositoryTestsCatalogue.java
@@ -25,6 +25,7 @@
*/
package org.alfresco;
+import org.alfresco.heartbeat.*;
import org.alfresco.repo.action.ActionConditionDefinitionImplTest;
import org.alfresco.repo.action.ActionConditionImplTest;
import org.alfresco.repo.action.ActionDefinitionImplTest;
@@ -417,6 +418,7 @@ public class AllRepositoryTestsCatalogue
suite.addTestSuite(ADMLuceneCategoryTest.class);
suite.addTestSuite(ADMLuceneTest.class);
suite.addTest(new JUnit4TestAdapter(org.alfresco.repo.blog.BlogServiceImplTest.class));
+ suite.addTest(new JUnit4TestAdapter(RepositoryDataCollectorTest.class));
}
// [classpath:alfresco/application-context.xml, classpath:cache-test/cache-test-context.xml]
@@ -587,6 +589,9 @@ public class AllRepositoryTestsCatalogue
// [alfresco/scheduler-core-context.xml, org/alfresco/util/test-scheduled-jobs-context.xml]
suite.addTest(new JUnit4TestAdapter(org.alfresco.util.CronTriggerBeanTest.class));
+
+ // [alfresco/scheduler-core-context.xml, org/alfresco/heartbeat/test-heartbeat-context.xml]
+ suite.addTest(new JUnit4TestAdapter(HeartBeatTest.class));
}
// no context - true JUNIT tests
@@ -716,5 +721,6 @@ public class AllRepositoryTestsCatalogue
suite.addTestSuite(WorklfowObjectFactoryTest.class);
suite.addTestSuite(WorkflowSuiteContextShutdownTest.class);
suite.addTest(new JUnit4TestAdapter(org.alfresco.repo.search.impl.lucene.analysis.PathTokenFilterTest.class));
+ suite.addTest(new JUnit4TestAdapter(HBDataCollectorServiceImplTest.class));
}
}
diff --git a/src/test/java/org/alfresco/heartbeat/HBDataCollectorServiceImplTest.java b/src/test/java/org/alfresco/heartbeat/HBDataCollectorServiceImplTest.java
new file mode 100644
index 0000000000..d658dc4a71
--- /dev/null
+++ b/src/test/java/org/alfresco/heartbeat/HBDataCollectorServiceImplTest.java
@@ -0,0 +1,127 @@
+/*
+ * #%L
+ * Alfresco Repository
+ * %%
+ * Copyright (C) 2005 - 2016 Alfresco Software Limited
+ * %%
+ * This file is part of the Alfresco software.
+ * If the software was purchased under a paid Alfresco license, the terms of
+ * the paid license agreement will prevail. Otherwise, the software is
+ * provided under the following open source license terms:
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ * #L%
+ */
+package org.alfresco.heartbeat;
+
+import org.alfresco.heartbeat.datasender.HBData;
+import org.alfresco.heartbeat.datasender.HBDataSenderService;
+import org.alfresco.service.cmr.repository.HBDataCollectorService;
+import org.junit.Before;
+import org.junit.Test;
+import java.util.Arrays;
+import java.util.List;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.internal.verification.VerificationModeFactory.times;
+
+/**
+ * @author eknizat
+ */
+public class HBDataCollectorServiceImplTest
+{
+
+ private HBDataCollectorServiceImpl dataCollectorService;
+ private HBDataSenderService mockDataSenderService;
+
+ @Before
+ public void setUp()
+ {
+ mockDataSenderService = mock(HBDataSenderService.class);
+ dataCollectorService = new HBDataCollectorServiceImpl(true);
+ dataCollectorService.setHbDataSenderService(mockDataSenderService);
+ }
+
+
+
+ @Test
+ public void testInitialEnabledEqualsDefaultState()
+ {
+ HBDataCollectorService dataCollectorService = new HBDataCollectorServiceImpl(true);
+ assertTrue(dataCollectorService.isEnabledByDefault());
+
+ dataCollectorService = new HBDataCollectorServiceImpl(false);
+ assertFalse(dataCollectorService.isEnabledByDefault());
+ }
+
+
+ @Test
+ public void testHBDataSenderServiceEnabledChange()
+ {
+ dataCollectorService.enabled(false);
+ verify(mockDataSenderService).enable(false);
+
+ dataCollectorService.enabled(true);
+ verify(mockDataSenderService).enable(true);
+ }
+
+ @Test
+ public void testCollectAndSendData()
+ {
+ // Set up dummy collector 1
+ HBData c1Data = new HBData("sys", "c1","1.0",null);
+ HBBaseDataCollector c1 = new DummyCollector(c1Data);
+ c1.setHbDataCollectorService(dataCollectorService);
+ c1.register();
+ // Set up dummy collector 2
+ HBData c2Data = new HBData("sys", "c2","1.0",null);
+ HBBaseDataCollector c2 = new DummyCollector(c2Data);
+ c2.setHbDataCollectorService(dataCollectorService);
+ c2.register();
+ // Set up dummy collector 3
+ HBData c3Data = new HBData("sys", "c3","1.0",null);
+ HBBaseDataCollector c3 = new DummyCollector(c3Data);
+ c3.setHbDataCollectorService(dataCollectorService);
+ c3.register();
+
+ // Check that the collector service collects data from registered collectors and passes the data to data sender service
+ dataCollectorService.collectAndSendData();
+
+ // Check data is passed for each collector
+ verify(mockDataSenderService, times(3)).sendData(any(List.class));
+ verify(mockDataSenderService).sendData(c1.collectData());
+ verify(mockDataSenderService).sendData(c2.collectData());
+ verify(mockDataSenderService).sendData(c3.collectData());
+ }
+
+
+ class DummyCollector extends HBBaseDataCollector
+ {
+ private HBData data;
+
+ public DummyCollector (HBData testData)
+ {
+ this.data = testData;
+ }
+
+ @Override
+ public List collectData() {
+ return Arrays.asList(data);
+ }
+ }
+
+}
diff --git a/src/test/java/org/alfresco/heartbeat/HeartBeatTest.java b/src/test/java/org/alfresco/heartbeat/HeartBeatTest.java
new file mode 100644
index 0000000000..9c8d8d0ebd
--- /dev/null
+++ b/src/test/java/org/alfresco/heartbeat/HeartBeatTest.java
@@ -0,0 +1,212 @@
+/*
+ * #%L
+ * Alfresco Repository
+ * %%
+ * Copyright (C) 2005 - 2016 Alfresco Software Limited
+ * %%
+ * This file is part of the Alfresco software.
+ * If the software was purchased under a paid Alfresco license, the terms of
+ * the paid license agreement will prevail. Otherwise, the software is
+ * provided under the following open source license terms:
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ * #L%
+ */
+package org.alfresco.heartbeat;
+
+import org.alfresco.heartbeat.datasender.HBDataSenderService;
+import org.alfresco.service.cmr.repository.HBDataCollectorService;
+import org.alfresco.service.license.LicenseDescriptor;
+import org.alfresco.service.license.LicenseService;
+import org.junit.Before;
+import org.junit.Test;
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+import java.util.Arrays;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * @author eknizat
+ */
+public class HeartBeatTest
+{
+
+ private static final String[] CONFIG_LOCATIONS = new String[] {
+ "classpath:alfresco/scheduler-core-context.xml",
+ "classpath:org/alfresco/heartbeat/test-heartbeat-context.xml"};
+ private ApplicationContext context;
+
+ LicenseService mockLicenseService;
+ HBDataSenderService mockDataSenderService;
+ HBDataCollectorService mockDataCollectorService;
+
+ @Before
+ public void setUp()
+ {
+ // New context with scheduler
+ context = new ClassPathXmlApplicationContext(CONFIG_LOCATIONS);
+
+ // Add services to context
+ mockLicenseService = mock(LicenseService.class);
+ mockDataCollectorService = mock(HBDataCollectorService.class);
+ ((ConfigurableApplicationContext) context).getBeanFactory().registerSingleton("licenseService",mockLicenseService);
+ ((ConfigurableApplicationContext) context).getBeanFactory().registerSingleton("hbDataCollectorService",mockDataCollectorService);
+ }
+
+ @Test
+ public void testHBRegistersWithLicenceService()
+ {
+ HeartBeat heartbeat = new HeartBeat(context,false);
+
+ // Check that HearBeat registers itself with the licence service
+ verify(mockLicenseService).registerOnLicenseChange(heartbeat);
+ }
+
+ @Test
+ public void testJobSchedulingWhenEnabled() throws Exception
+ {
+ // Enable heartbeat in data collector service ( as if set in prop file)
+ when(mockDataCollectorService.isEnabledByDefault()).thenReturn(true);
+
+ HeartBeat heartbeat = new HeartBeat(context,true);
+
+ // Check that the job is scheduled when heartbeat is enabled
+ assertTrue("Job was not scheduled but HB is enabled", isJobScheduled());
+ }
+
+ @Test
+ public void testJobSchedulingWhenDisabled() throws Exception
+ {
+ // Disable heartbeat in data collector service ( as if set in prop file)
+ when(mockDataCollectorService.isEnabledByDefault()).thenReturn(false);
+
+ HeartBeat heartbeat = new HeartBeat(context,true);
+
+ // Check that the job is not scheduled when heartbeat is disabled
+ assertFalse("Job was scheduled but HB is disabled", isJobScheduled());
+ }
+
+ /**
+ * Heartbeat enabled by default but disabled in licence on onLicenseChange
+ */
+ @Test
+ public void testOnLicenseChangeOverridesDefaultEnabled() throws Exception
+ {
+ // Enable heartbeat in data collector service ( as if set in prop file)
+ when(mockDataCollectorService.isEnabledByDefault()).thenReturn(true);
+
+ HeartBeat heartbeat = new HeartBeat(context,true);
+
+ // heartbeat disabled in licence
+ LicenseDescriptor mockLicenseDescriptor = mock(LicenseDescriptor.class);
+ when(mockLicenseDescriptor.isHeartBeatDisabled()).thenReturn(true);
+
+ assertTrue(heartbeat.isEnabled());
+ assertTrue("Job should be scheduled at this point.",isJobScheduled());
+
+ heartbeat.onLicenseChange(mockLicenseDescriptor);
+
+ // Check heartbeat is disabled and job unscheduled
+ assertFalse(heartbeat.isEnabled());
+ assertFalse("Job should be unscheduled.",isJobScheduled());
+ }
+
+ /**
+ * heartbeat disabled by default but enabled in licence
+ */
+ @Test
+ public void testOnLicenseChangeOverridesDefaultDisabled() throws Exception
+ {
+ // Disable heartbeat in data collector service ( as if set in prop file)
+ when(mockDataCollectorService.isEnabledByDefault()).thenReturn(false);
+
+ HeartBeat heartbeat = new HeartBeat(context,true);
+
+ // heartbeat enabled in licence
+ LicenseDescriptor mockLicenseDescriptor = mock(LicenseDescriptor.class);
+ when(mockLicenseDescriptor.isHeartBeatDisabled()).thenReturn(false);
+
+ assertFalse(heartbeat.isEnabled());
+ assertFalse("Job should not be scheduled at this point.",isJobScheduled());
+
+ heartbeat.onLicenseChange(mockLicenseDescriptor);
+
+ // Check heartbeat is enabled and job unscheduled
+ assertTrue(heartbeat.isEnabled());
+ assertTrue("Job should be scheduled.",isJobScheduled());
+ }
+
+ @Test
+ public void testOnLicenceFailRevertsToEnabled() throws Exception
+ {
+ // Enable heartbeat in data collector service ( as if set in prop file)
+ when(mockDataCollectorService.isEnabledByDefault()).thenReturn(true);
+
+ HeartBeat heartbeat = new HeartBeat(context,true);
+
+ // heartbeat disabled in licence
+ LicenseDescriptor mockLicenseDescriptor = mock(LicenseDescriptor.class);
+ when(mockLicenseDescriptor.isHeartBeatDisabled()).thenReturn(true);
+ heartbeat.onLicenseChange(mockLicenseDescriptor);
+
+ assertFalse(heartbeat.isEnabled());
+ assertFalse("Job should not be scheduled at this point.",isJobScheduled());
+
+ // Revert back to default state
+ heartbeat.onLicenseFail();
+
+ // Check heartbeat is enabled and job unscheduled
+ assertTrue(heartbeat.isEnabled());
+ assertTrue("Job should be unscheduled.",isJobScheduled());
+ }
+
+ @Test
+ public void testOnLicenceFailRevertsToDisabled() throws Exception
+ {
+ // Disable heartbeat in data collector service ( as if set in prop file)
+ when(mockDataCollectorService.isEnabledByDefault()).thenReturn(false);
+
+ HeartBeat heartbeat = new HeartBeat(context,true);
+
+ // heartbeat enabled in licence
+ LicenseDescriptor mockLicenseDescriptor = mock(LicenseDescriptor.class);
+ when(mockLicenseDescriptor.isHeartBeatDisabled()).thenReturn(false);
+ heartbeat.onLicenseChange(mockLicenseDescriptor);
+
+ assertTrue(heartbeat.isEnabled());
+ assertTrue("Job should be scheduled at this point.",isJobScheduled());
+
+ // Revert back to default state
+ heartbeat.onLicenseFail();
+
+ // Check heartbeat is disabled and job unscheduled
+ assertFalse(heartbeat.isEnabled());
+ assertFalse("Job should be unscheduled.",isJobScheduled());
+ }
+
+ private boolean isJobScheduled() throws Exception
+ {
+ Scheduler scheduler = (Scheduler) context.getBean("schedulerFactory");
+ String[] jobs = scheduler.getJobNames( Scheduler.DEFAULT_GROUP);
+ return Arrays.asList(jobs).contains("heartbeat");
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/org/alfresco/heartbeat/RepositoryDataCollectorTest.java b/src/test/java/org/alfresco/heartbeat/RepositoryDataCollectorTest.java
new file mode 100644
index 0000000000..8c576f54a3
--- /dev/null
+++ b/src/test/java/org/alfresco/heartbeat/RepositoryDataCollectorTest.java
@@ -0,0 +1,155 @@
+/*
+ * #%L
+ * Alfresco Repository
+ * %%
+ * Copyright (C) 2005 - 2016 Alfresco Software Limited
+ * %%
+ * This file is part of the Alfresco software.
+ * If the software was purchased under a paid Alfresco license, the terms of
+ * the paid license agreement will prevail. Otherwise, the software is
+ * provided under the following open source license terms:
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ * #L%
+ */
+package org.alfresco.heartbeat;
+
+import org.alfresco.heartbeat.datasender.HBData;
+import org.alfresco.repo.descriptor.DescriptorDAO;
+import org.alfresco.repo.dictionary.CustomModelsInfo;
+import org.alfresco.repo.usage.RepoUsageComponent;
+import org.alfresco.service.cmr.admin.RepoUsage;
+import org.alfresco.service.cmr.dictionary.CustomModelService;
+import org.alfresco.service.cmr.repository.HBDataCollectorService;
+import org.alfresco.service.cmr.security.AuthorityService;
+import org.alfresco.service.descriptor.Descriptor;
+import org.alfresco.service.transaction.TransactionService;
+import org.alfresco.util.ApplicationContextHelper;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.context.ApplicationContext;
+import java.util.List;
+import java.util.Map;
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * @author eknizat
+ */
+public class RepositoryDataCollectorTest
+{
+
+ private ApplicationContext context;
+ private RepositoryDataCollector repoCollector;
+ private List collectedData;
+
+ @Before
+ public void setUp()
+ {
+ context = ApplicationContextHelper.getApplicationContext();
+
+ TransactionService transactionService = (TransactionService) context.getBean("transactionService");
+ HBDataCollectorService mockCollectorService = mock(HBDataCollectorService.class);
+ AuthorityService authorityService = mock(AuthorityService.class);
+
+ Descriptor mockDescriptor = mock(Descriptor.class);
+ when(mockDescriptor.getId()).thenReturn("mock_id");
+ DescriptorDAO descriptorDAO = mock(DescriptorDAO.class);
+ when(descriptorDAO.getDescriptor()).thenReturn(mockDescriptor);
+
+ RepoUsage mockRepoUsage = mock(RepoUsage.class);
+ RepoUsageComponent repoUsageComponent = mock(RepoUsageComponent.class);
+ when(repoUsageComponent.getUsage()).thenReturn(mockRepoUsage);
+
+ CustomModelsInfo mockCustomModelsInfo = mock(CustomModelsInfo.class);
+ CustomModelService customModelService = mock(CustomModelService.class);
+ when(customModelService.getCustomModelsInfo()).thenReturn(mockCustomModelsInfo);
+
+ repoCollector = new RepositoryDataCollector();
+ repoCollector.setAuthorityService(authorityService);
+ repoCollector.setCurrentRepoDescriptorDAO(descriptorDAO);
+ repoCollector.setCustomModelService(customModelService);
+ repoCollector.setRepoUsageComponent(repoUsageComponent);
+ repoCollector.setServerDescriptorDAO(descriptorDAO);
+ repoCollector.setTransactionService(transactionService);
+ repoCollector.setHbDataCollectorService(mockCollectorService);
+ collectedData = repoCollector.collectData();
+ }
+
+ @Test
+ public void testHBDataFields()
+ {
+ for(HBData data : this.collectedData)
+ {
+ assertNotNull(data.getCollectorId());
+ assertNotNull(data.getCollectorVersion());
+ assertNotNull(data.getSchemaVersion());
+ assertNotNull(data.getSystemId());
+ assertNotNull(data.getTimestamp());
+ }
+
+ }
+
+ @Test
+ public void testInfoDataIsCollected()
+ {
+ HBData repoInfo = grabDataByCollectorId("acs.repository.info");
+ assertNotNull("Repository info data missing.", repoInfo);
+
+ Map data = repoInfo.getData();
+ assertTrue(data.containsKey("repoName"));
+ assertTrue(data.containsKey("edition"));
+ assertTrue(data.containsKey("versionMajor"));
+ assertTrue(data.containsKey("versionMinor"));
+ assertTrue(data.containsKey("schema"));
+ }
+
+ @Test
+ public void testSystemUsageDataIsCollected()
+ {
+ HBData systemUsage = grabDataByCollectorId("acs.repository.usage.system");
+ assertNotNull("Repository usage data missing.", systemUsage);
+
+ Map data = systemUsage.getData();
+ assertTrue(data.containsKey("memFree"));
+ assertTrue(data.containsKey("memMax"));
+ assertTrue(data.containsKey("memTotal"));
+ }
+
+ @Test
+ public void testModelUsageDataIsCollected()
+ {
+ HBData modelUsage = grabDataByCollectorId("acs.repository.usage.model");
+ assertNotNull("Model usage data missing.", modelUsage);
+
+ Map data = modelUsage.getData();
+ assertTrue(data.containsKey("numOfActiveModels"));
+ assertTrue(data.containsKey("numOfActiveTypes"));
+ assertTrue(data.containsKey("numOfActiveAspects"));
+
+ }
+
+ private HBData grabDataByCollectorId(String collectorId)
+ {
+ for (HBData d : this.collectedData)
+ {
+ if(d.getCollectorId()!=null && d.getCollectorId().equals(collectorId))
+ {
+ return d;
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/test/resources/org/alfresco/heartbeat/test-heartbeat-context.xml b/src/test/resources/org/alfresco/heartbeat/test-heartbeat-context.xml
new file mode 100644
index 0000000000..b3eb65d2d1
--- /dev/null
+++ b/src/test/resources/org/alfresco/heartbeat/test-heartbeat-context.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+ true
+
+
+ org.alfresco.repo.scheduler.AlfrescoSchedulerFactory
+
+
+
+
+
+
+
+
+ DefaultScheduler
+
+
+
+ false
+
+
+
+