diff --git a/src/main/java/org/alfresco/heartbeat/CommunityHBDataCollector.java b/src/main/java/org/alfresco/heartbeat/CommunityHBDataCollector.java
new file mode 100644
index 0000000000..c9e336d3d2
--- /dev/null
+++ b/src/main/java/org/alfresco/heartbeat/CommunityHBDataCollector.java
@@ -0,0 +1,59 @@
+/*
+ * #%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 org.alfresco.service.cmr.repository.HBDataCollectorService;
+import org.springframework.context.ApplicationContext;
+
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+public class CommunityHBDataCollector extends HBBaseDataCollector {
+
+
+
+ public CommunityHBDataCollector (HBDataCollectorService dataCollectorService)
+ {
+ super(dataCollectorService);
+ }
+
+
+
+
+ @Override
+ public List collectData()
+ {
+
+ String timeStamp = new SimpleDateFormat("YYYY-MM-dd'T'HH:mm:ss.sss'Z'").format(new Date());
+
+ // Collect some data
+ HBData data = new HBData("Community_sys_id","Community_collector_id","Community_collector_v", timeStamp);
+
+ return Arrays.asList(data);
+ }
+}
diff --git a/src/main/java/org/alfresco/heartbeat/HBBaseDataCollector.java b/src/main/java/org/alfresco/heartbeat/HBBaseDataCollector.java
index 75a462ff27..d1dc52c24d 100644
--- a/src/main/java/org/alfresco/heartbeat/HBBaseDataCollector.java
+++ b/src/main/java/org/alfresco/heartbeat/HBBaseDataCollector.java
@@ -32,8 +32,12 @@ import org.alfresco.service.cmr.repository.HBDataCollectorService;
public abstract class HBBaseDataCollector
{
private HBDataCollectorService hbDataCollectorService;
-
-
+
+ public HBBaseDataCollector( HBDataCollectorService dataCollectorService ) {
+ this.hbDataCollectorService = dataCollectorService;
+ this.register(); // I'v moved the registering here assuming every collector will do the same?
+ }
+
public void register()
{
hbDataCollectorService.registerCollector(this);
diff --git a/src/main/java/org/alfresco/heartbeat/HBData.java b/src/main/java/org/alfresco/heartbeat/HBData.java
index 997653ed9f..dd7ea9f59f 100644
--- a/src/main/java/org/alfresco/heartbeat/HBData.java
+++ b/src/main/java/org/alfresco/heartbeat/HBData.java
@@ -138,6 +138,12 @@ public class HBData
this.data = data;
}
+ @Override
+ public String toString()
+ {
+ return "HBData(" + systemId + " " + collectorId + " " + collectorVersion + " " + timestamp +")";
+ }
+
public static void main(String[] args) throws JSONException
{
Map data = new TreeMap();
diff --git a/src/main/java/org/alfresco/heartbeat/HBDataCollectorServiceImpl.java b/src/main/java/org/alfresco/heartbeat/HBDataCollectorServiceImpl.java
index faf89651d2..bf5e1a6608 100644
--- a/src/main/java/org/alfresco/heartbeat/HBDataCollectorServiceImpl.java
+++ b/src/main/java/org/alfresco/heartbeat/HBDataCollectorServiceImpl.java
@@ -29,13 +29,21 @@ import java.util.LinkedList;
import java.util.List;
import org.alfresco.service.cmr.repository.HBDataCollectorService;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.context.ApplicationContext;
public class HBDataCollectorServiceImpl implements HBDataCollectorService
{
+
+ /** The logger. */
+ private static final Log logger = LogFactory.getLog(HBDataCollectorServiceImpl.class);
+
private List collectors = new LinkedList<>();
// private HBDataSenderService dataSender;
private boolean enabled;
+
@Override
public void registerCollector(HBBaseDataCollector collector)
{
@@ -47,7 +55,9 @@ public class HBDataCollectorServiceImpl implements HBDataCollectorService
{
for (HBBaseDataCollector collector : collectors)
{
+
List data = collector.collectData();
+
// try
// {
// dataSender.sendData(data);
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..a4c34c9a99
--- /dev/null
+++ b/src/main/java/org/alfresco/heartbeat/HeartBeat.java
@@ -0,0 +1,320 @@
+/*
+ * #%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.repo.descriptor.DescriptorDAO;
+import org.alfresco.repo.dictionary.CustomModelsInfo;
+import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
+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.cmr.security.AuthorityType;
+import org.alfresco.service.descriptor.Descriptor;
+import org.alfresco.service.license.LicenseDescriptor;
+import org.alfresco.service.license.LicenseException;
+import org.alfresco.service.license.LicenseService;
+import org.alfresco.service.license.LicenseService.LicenseChangeHandler;
+import org.alfresco.service.transaction.TransactionService;
+import org.alfresco.traitextender.SpringExtensionBundle;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.quartz.*;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.extensions.surf.util.Base64;
+
+import javax.sql.DataSource;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStreamWriter;
+import java.lang.reflect.Method;
+import java.net.HttpURLConnection;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.URL;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.cert.Certificate;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+/**
+ * This class communicates some very basic repository statistics to Alfresco on a regular basis.
+ *
+ * @author dward
+ */
+public class HeartBeat implements LicenseChangeHandler
+{
+
+ private static final String LAMBDA_INGEST_URL = "https://0s910f9ijc.execute-api.eu-west-1.amazonaws.com/Stage/ingest";
+
+ /** The default enable state */
+ private static final boolean DEFAULT_HEARTBEAT_ENABLED = true;
+
+ /** The logger. */
+ private static final Log logger = LogFactory.getLog(HeartBeat.class);
+
+ private LicenseService licenseService;
+
+ private Scheduler scheduler;
+
+ /** URL to post heartbeat to. */
+ private String heartBeatUrl;
+
+ private boolean testMode = true;
+
+ private final String JOB_NAME = "heartbeat";
+
+ /** Is the heartbeat enabled */
+ private boolean enabled = DEFAULT_HEARTBEAT_ENABLED;
+
+ private HBDataCollectorService dataCollectorService;
+
+
+
+ /**
+ * 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, true);
+ }
+
+ /**
+ * 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 testModel)
+ {
+ logger.debug("Initialising HeartBeat");
+
+
+ // I think these should be wired by spring instead for proper ioc..
+ this.dataCollectorService = (HBDataCollectorService) context.getBean("hbDataCollectorService");
+ this.scheduler = (Scheduler) context.getBean("schedulerFactory");
+
+ this.testMode = testModel;
+
+ 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);
+ }
+ }
+
+ private synchronized void setHeartBeatUrl(String heartBeatUrl)
+ {
+ this.heartBeatUrl = heartBeatUrl;
+ }
+
+ // Determine the URL to send the heartbeat to from the license if not set
+ private synchronized String getHeartBeatUrl()
+ {
+ if (heartBeatUrl == null)
+ {
+ // GC: Ignore the standard heartbeat URL and always use the AWS/Lambda URL
+// LicenseDescriptor licenseDescriptor = licenseService.getLicense();
+// String url = (licenseDescriptor == null) ? null : licenseDescriptor.getHeartBeatUrl();
+// setHeartBeatUrl(url == null ? HeartBeat.DEFAULT_URL : url);
+ setHeartBeatUrl(LAMBDA_INGEST_URL);
+ }
+
+ logger.debug("Returning heartBeatUrl: " + heartBeatUrl);
+
+ return heartBeatUrl;
+ }
+
+ /**
+ * @return true if the heartbeat is currently enabled
+ */
+ public synchronized boolean isEnabled()
+ {
+ return enabled;
+ }
+
+
+
+ /**
+ * Sends encrypted data over HTTP.
+ *
+ * @throws IOException
+ * Signals that an I/O exception has occurred.
+ * @throws GeneralSecurityException
+ * an encryption related exception
+ */
+ public void sendData() throws IOException, GeneralSecurityException
+ {
+ this.dataCollectorService.collectAndSendData();
+ }
+
+ /**
+ * Listens for license changes. If a license is change or removed, the heartbeat job is resheduled.
+ */
+ public synchronized void onLicenseChange(LicenseDescriptor licenseDescriptor)
+ {
+ logger.debug("Update license called");
+
+ setHeartBeatUrl(licenseDescriptor.getHeartBeatUrl());
+ boolean newEnabled = !licenseDescriptor.isHeartBeatDisabled();
+
+ if (newEnabled != enabled)
+ {
+ logger.debug("State change of heartbeat");
+ this.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 = DEFAULT_HEARTBEAT_ENABLED;
+
+ if (newEnabled != enabled)
+ {
+ logger.debug("State change of heartbeat");
+ this.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
+ if(enabled)
+ {
+ 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
+ final String triggerName = JOB_NAME + "Trigger";
+ 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
+ {
+ logger.debug("heartbeat job unscheduled");
+ final String triggerName = JOB_NAME + "Trigger";
+ 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.sendData();
+ }
+ 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/repo/descriptor/DescriptorServiceImpl.java b/src/main/java/org/alfresco/repo/descriptor/DescriptorServiceImpl.java
index a928c23416..20d3db6bcc 100644
--- a/src/main/java/org/alfresco/repo/descriptor/DescriptorServiceImpl.java
+++ b/src/main/java/org/alfresco/repo/descriptor/DescriptorServiceImpl.java
@@ -1,34 +1,35 @@
-/*
- * #%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;
import java.lang.reflect.Constructor;
import org.alfresco.error.AlfrescoRuntimeException;
+import org.alfresco.heartbeat.HBBaseDataCollector;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
@@ -37,6 +38,7 @@ import org.alfresco.repo.usage.RepoUsageComponent;
import org.alfresco.service.cmr.admin.RepoUsage;
import org.alfresco.service.cmr.admin.RepoUsage.LicenseMode;
import org.alfresco.service.cmr.admin.RepoUsage.UsageType;
+import org.alfresco.service.cmr.repository.HBDataCollectorService;
import org.alfresco.service.descriptor.Descriptor;
import org.alfresco.service.descriptor.DescriptorService;
import org.alfresco.service.license.LicenseDescriptor;
@@ -303,9 +305,10 @@ 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.enterprise.heartbeat.HeartBeat");
+ heartBeat = constructSpecialService("org.alfresco.heartbeat.HeartBeat");
// Now listen for future license changes
licenseService.registerOnLicenseChange(this);
diff --git a/src/main/resources/alfresco/bootstrap-context.xml b/src/main/resources/alfresco/bootstrap-context.xml
index 451d23ab3b..5077a73f3f 100644
--- a/src/main/resources/alfresco/bootstrap-context.xml
+++ b/src/main/resources/alfresco/bootstrap-context.xml
@@ -269,7 +269,7 @@
-
+
@@ -287,6 +287,19 @@
+
+
+
+
+
+
+
+
+
+
+