diff --git a/.gitignore b/.gitignore
index 5a22d86f8..4fd80ed64 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,7 +12,7 @@ argus-build.properties
.settings/
.DS_Store
/config
-
+ArgusKafkaConsumer/dependency-reduced-pom.xml
ArgusCore/bloomstate/*
# Eclipse ignore
@@ -43,6 +43,9 @@ local.properties
*.ipr
*.iws
+#vscode
+.vscode/
+
Makefile
property
diff --git a/.travis.yml b/.travis.yml
index a25ec8670..ccc1b43d4 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,5 +1,6 @@
language: java
jdk: oraclejdk8
+dist: trusty
sudo: required
group: deprecated-2017Q2
env:
diff --git a/ArgusClient/pom.xml b/ArgusClient/pom.xml
index 8acfaebd4..3b5b86da3 100644
--- a/ArgusClient/pom.xml
+++ b/ArgusClient/pom.xml
@@ -4,16 +4,20 @@
arguscom.salesforce.argus
- 2.22.3
+ 4-SNAPSHOT..
+
argus-client
+ 4.75-SNAPSHOTjarArgusClientCommand line client for the Argus Production Monitoring Tool.${project.basedir}/${project.parent.relativePath}
+ **
+
@@ -81,6 +85,28 @@
test-resources
+
+ copy
+ package
+
+ copy
+
+
+
+
+ io.prometheus.jmx
+ jmx_prometheus_javaagent
+ ${jmx.prometheus.version}
+ jar
+ sources
+ true
+ ${project.build.directory}
+ jmx_prometheus_javaagent-${jmx.prometheus.version}.jar
+
+
+
+
+
@@ -142,30 +168,30 @@
-
- ${project.groupId}
- argus
- ${project.version}
- resources
- zip
- provided
-
-
- ${project.groupId}
- argus
- ${project.version}
- test-resources
- zip
- provided
-
-
- junit
- junit
-
-
- ${project.groupId}
- argus-core
- ${project.version}
-
+
+ ${project.groupId}
+ argus
+ ${project.parent.version}
+ resources
+ zip
+ provided
+
+
+ ${project.groupId}
+ argus
+ ${project.parent.version}
+ test-resources
+ zip
+ provided
+
+
+ junit
+ junit
+
+
+ com.salesforce.argus
+ argus-core
+ 4.75-SNAPSHOT
+
diff --git a/ArgusClient/src/main/java/com/salesforce/dva/argus/client/Alerter.java b/ArgusClient/src/main/java/com/salesforce/dva/argus/client/Alerter.java
index 3c61080d6..4eb2cd938 100644
--- a/ArgusClient/src/main/java/com/salesforce/dva/argus/client/Alerter.java
+++ b/ArgusClient/src/main/java/com/salesforce/dva/argus/client/Alerter.java
@@ -78,8 +78,12 @@ class Alerter implements Runnable {
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
- jobCounter.addAndGet(service.executeScheduledAlerts(50, timeout).size());
- LOGGER.info("alerts evaluated so far: {}", jobCounter.get());
+ int currentAlertCount = jobCounter.get();
+ jobCounter.addAndGet(service.executeScheduledAlerts(50, timeout));
+
+ if(jobCounter.get() != currentAlertCount) {
+ LOGGER.info("alerts evaluated so far: {}", jobCounter.get());
+ }
Thread.sleep(POLL_INTERVAL_MS);
} catch (InterruptedException ex) {
LOGGER.info("Execution was interrupted.");
@@ -89,7 +93,7 @@ public void run() {
LOGGER.error("Exception in alerter: {}", ExceptionUtils.getFullStackTrace(ex));
}
}
- LOGGER.warn(MessageFormat.format("Alerter thread interrupted. {} alerts evaluated by this thread.", jobCounter.get()));
+ LOGGER.warn("Alerter thread interrupted. {} alerts evaluated by this thread.", jobCounter.get());
service.dispose();
}
}
diff --git a/ArgusClient/src/main/java/com/salesforce/dva/argus/client/AnnotationCommitter.java b/ArgusClient/src/main/java/com/salesforce/dva/argus/client/AnnotationCommitter.java
index bf294a730..9b60cf64c 100644
--- a/ArgusClient/src/main/java/com/salesforce/dva/argus/client/AnnotationCommitter.java
+++ b/ArgusClient/src/main/java/com/salesforce/dva/argus/client/AnnotationCommitter.java
@@ -70,7 +70,7 @@ public void run() {
int count = collectionService.commitAnnotations(ANNOTATION_CHUNK_SIZE, TIMEOUT);
if (count > 0) {
- LOGGER.info(MessageFormat.format("Committed {0} annotations.", count));
+ LOGGER.info("Committed {} annotations.", count);
jobCounter.addAndGet(count);
}
Thread.sleep(POLL_INTERVAL_MS);
@@ -82,7 +82,7 @@ public void run() {
LOGGER.info("Error occurred while committing annotations. Reason {}", ex.toString());
}
}
- LOGGER.warn(MessageFormat.format("Annotation committer thread interrupted. {} annotations committed by this thread.", jobCounter.get()));
+ LOGGER.warn("Annotation committer thread interrupted. {} annotations committed by this thread.", jobCounter.get());
collectionService.dispose();
monitorService.dispose();
}
diff --git a/ArgusClient/src/main/java/com/salesforce/dva/argus/client/ClientServiceFactory.java b/ArgusClient/src/main/java/com/salesforce/dva/argus/client/ClientServiceFactory.java
index 02e45716e..31fe883ed 100644
--- a/ArgusClient/src/main/java/com/salesforce/dva/argus/client/ClientServiceFactory.java
+++ b/ArgusClient/src/main/java/com/salesforce/dva/argus/client/ClientServiceFactory.java
@@ -48,23 +48,34 @@ class ClientServiceFactory {
//~ Methods **************************************************************************************************************************************
- static ExecutorService startClientService(SystemMain system, ClientType clientType, AtomicInteger jobCounter) {
+ static ExecutorService[] startClientService(SystemMain system, ClientType clientType, AtomicInteger jobCounter) {
switch (clientType) {
case ALERT:
- return startAlertClientService(system, jobCounter);
+ return collect(startAlertClientService(system, jobCounter),
+ startRefocusClientService(system));
case COMMIT_SCHEMA:
/* Alpha feature, not currently supported. */
- return startCommitSchemaClientService(system, jobCounter);
+ return collect(startCommitSchemaClientService(system, jobCounter));
case COMMIT_ANNOTATIONS:
- return startCommitAnnotationsClientService(system, jobCounter);
+ return collect(startCommitAnnotationsClientService(system, jobCounter));
+ case COMMIT_HISTOGRAMS:
+ return collect(startCommitHistogramsClientService(system, jobCounter));
case PROCESS_QUERIES:
- return startProcessMetricsClientService(system, jobCounter);
+ return collect(startProcessMetricsClientService(system, jobCounter));
default:
- return startCommitMetricsClientService(system, jobCounter);
+ return collect(startCommitMetricsClientService(system, jobCounter));
}
}
+
+ private static ExecutorService[] collect(ExecutorService ... services)
+ {
+ ExecutorService[] rv = new ExecutorService[services.length];
+ System.arraycopy(services, 0, rv, 0, services.length);
+ return rv;
+ }
+
private static ExecutorService startAlertClientService(SystemMain system, AtomicInteger jobCounter) {
int configuredCount = Integer.valueOf(system.getConfiguration().getValue(SystemConfiguration.Property.CLIENT_THREADS));
int configuredTimeout = Integer.valueOf(system.getConfiguration().getValue(SystemConfiguration.Property.CLIENT_CONNECT_TIMEOUT));
@@ -86,6 +97,28 @@ public Thread newThread(Runnable r) {
return service;
}
+ private static ExecutorService startRefocusClientService(SystemMain system) {
+ int configuredCount = Integer.valueOf(system.getConfiguration().getValue(SystemConfiguration.Property.REFOCUS_CLIENT_THREADS));
+ int configuredTimeout = Integer.valueOf(system.getConfiguration().getValue(SystemConfiguration.Property.REFOCUS_CLIENT_CONNECT_TIMEOUT));
+ int threadPoolCount = Math.max(configuredCount, 1); // TODO - why any other value than 1?
+ // todo - No need for tpc>1 thread until threads added for executing the HTTP requests.
+ int timeout = Math.max(10000, configuredTimeout);
+ ExecutorService service = Executors.newFixedThreadPool(threadPoolCount, new ThreadFactory() {
+
+ AtomicInteger id = new AtomicInteger(0);
+
+ @Override
+ public Thread newThread(Runnable r) {
+ return new Thread(r, MessageFormat.format("refocusclient-{0}", id.getAndIncrement()));
+ }
+ });
+ system.getServiceFactory().getMonitorService().startRecordingCounters();
+ for (int i = 0; i < threadPoolCount; i++) {
+ service.submit(new Refocuser(system.getServiceFactory().getRefocusService(), timeout));
+ }
+ return service;
+ }
+
private static ExecutorService startCommitAnnotationsClientService(SystemMain system, AtomicInteger jobCounter) {
int configuredCount = Integer.valueOf(system.getConfiguration().getValue(SystemConfiguration.Property.CLIENT_THREADS));
int threadPoolCount = Math.max(configuredCount, 2);
@@ -104,7 +137,26 @@ public Thread newThread(Runnable r) {
}
return service;
}
+
+ private static ExecutorService startCommitHistogramsClientService(SystemMain system, AtomicInteger jobCounter) {
+ int configuredCount = Integer.valueOf(system.getConfiguration().getValue(SystemConfiguration.Property.CLIENT_THREADS));
+ int threadPoolCount = Math.max(configuredCount, 2);
+ ExecutorService service = Executors.newFixedThreadPool(threadPoolCount, new ThreadFactory() {
+
+ AtomicInteger id = new AtomicInteger(0);
+ @Override
+ public Thread newThread(Runnable r) {
+ return new Thread(r, MessageFormat.format("histogramcommitclient-{0}", id.getAndIncrement()));
+ }
+ });
+ system.getServiceFactory().getMonitorService().startRecordingCounters();
+ for (int i = 0; i < threadPoolCount; i++) {
+ service.submit(new HistogramCommitter(system.getServiceFactory().getCollectionService(),system.getServiceFactory().getMonitorService(), jobCounter));
+ }
+ return service;
+ }
+
private static ExecutorService startCommitMetricsClientService(SystemMain system, AtomicInteger jobCounter) {
int configuredCount = Integer.valueOf(system.getConfiguration().getValue(SystemConfiguration.Property.CLIENT_THREADS));
int threadPoolCount = Math.max(configuredCount, 2);
@@ -136,7 +188,7 @@ public Thread newThread(Runnable r) {
return new Thread(r, MessageFormat.format("schemacommitclient-{0}", id.getAndIncrement()));
}
});
- system.getServiceFactory().getMonitorService().startRecordingCounters();
+ system.getServiceFactory().getMonitorService().startRecordingCounters();
for (int i = 0; i < threadPoolCount; i++) {
service.submit(new SchemaCommitter(system.getServiceFactory().getCollectionService(),system.getServiceFactory().getMonitorService(), jobCounter));
}
diff --git a/ArgusClient/src/main/java/com/salesforce/dva/argus/client/ClientType.java b/ArgusClient/src/main/java/com/salesforce/dva/argus/client/ClientType.java
index d7a645632..97ea1d5ec 100644
--- a/ArgusClient/src/main/java/com/salesforce/dva/argus/client/ClientType.java
+++ b/ArgusClient/src/main/java/com/salesforce/dva/argus/client/ClientType.java
@@ -42,6 +42,7 @@ enum ClientType {
COMMIT_METRICS,
COMMIT_ANNOTATIONS,
+ COMMIT_HISTOGRAMS,
ALERT,
/* Alpha feature, not currently supported. */
COMMIT_SCHEMA,
diff --git a/ArgusClient/src/main/java/com/salesforce/dva/argus/client/HistogramCommitter.java b/ArgusClient/src/main/java/com/salesforce/dva/argus/client/HistogramCommitter.java
new file mode 100644
index 000000000..4f2c03855
--- /dev/null
+++ b/ArgusClient/src/main/java/com/salesforce/dva/argus/client/HistogramCommitter.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2019, Salesforce.com, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of Salesforce.com nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.salesforce.dva.argus.client;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import com.salesforce.dva.argus.service.CollectionService;
+import com.salesforce.dva.argus.service.MonitorService;
+
+/**
+ * Commits histograms from the submit queue into persistent storage.
+ *
+ * @author Dilip Devaraj (ddevaraj@salesforce.com)
+ */
+public class HistogramCommitter extends AbstractCommitter {
+
+ //~ Static fields/initializers *******************************************************************************************************************
+
+ private static final int HISTOGRAM_MESSAGES_CHUNK_SIZE = 100;
+ //~ Constructors *********************************************************************************************************************************
+
+ /**
+ * Creates a new HistogramCommitter object.
+ *
+ * @param collectionService The collection service to use. Cannot be null.
+ * @param monitorService The monitoring service to use. Cannot be null.
+ * @param jobCounter The global job counter used to track the number of histograms.
+ */
+ HistogramCommitter(CollectionService colletionService, MonitorService monitorService, AtomicInteger jobCounter) {
+ super(colletionService,monitorService, jobCounter);
+ }
+
+ //~ Methods **************************************************************************************************************************************
+
+ @Override
+ public void run() {
+ while (!Thread.currentThread().isInterrupted()) {
+ try {
+ int count = collectionService.commitHistograms(HISTOGRAM_MESSAGES_CHUNK_SIZE, TIMEOUT);
+
+ if (count > 0) {
+ LOGGER.info("Committed {} histograms.", count);
+ jobCounter.addAndGet(count);
+ }
+ Thread.sleep(POLL_INTERVAL_MS);
+ } catch (InterruptedException ie) {
+ LOGGER.info("Execution was interrupted.");
+ Thread.currentThread().interrupt();
+ break;
+ } catch (Throwable ex) {
+ LOGGER.info("Error occurred while committing histograms. Reason {}", ex.toString());
+ }
+ }
+ LOGGER.warn("Histogram committer thread interrupted. {} histograms committed by this thread.", jobCounter.get());
+ collectionService.dispose();
+ monitorService.dispose();
+ }
+}
+/* Copyright (c) 2019, Salesforce.com, Inc. All rights reserved. */
diff --git a/ArgusClient/src/main/java/com/salesforce/dva/argus/client/Main.java b/ArgusClient/src/main/java/com/salesforce/dva/argus/client/Main.java
index 2db44171e..6aee062ed 100644
--- a/ArgusClient/src/main/java/com/salesforce/dva/argus/client/Main.java
+++ b/ArgusClient/src/main/java/com/salesforce/dva/argus/client/Main.java
@@ -48,7 +48,6 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
-import static com.salesforce.dva.argus.client.ClientType.COMMIT_METRICS;
import static com.salesforce.dva.argus.util.Option.findOption;
/**
@@ -72,7 +71,7 @@ public class Main {
static {
HELP_OPTION = Option.createFlag("-h", "Display the usage and available collector types.");
TYPE_OPTION = Option.createOption("-t",
- "[ COMMIT_METRICS | COMMIT_ANNOTATIONS | ALERT | COMMIT_SCHEMA ] Specifies the type of client to create. Default is COMMIT_METRICS");
+ "[ COMMIT_METRICS | COMMIT_ANNOTATIONS | ALERT | COMMIT_SCHEMA | COMMIT_HISTOGRAMS] Specifies the type of client to create. Default is COMMIT_METRICS");
INSTALL_OPTION = Option.createOption("-i", " Specifies a file location to store a configuration created interactively.");
CONFIG_OPTION = Option.createOption("-f", " Specifies the configuration file to use.");
TEMPLATES = new Option[] { HELP_OPTION, TYPE_OPTION, INSTALL_OPTION, CONFIG_OPTION };
@@ -162,9 +161,14 @@ static void main(String[] args, PrintStream out) throws IOException {
ClientType type;
try {
- type = typeOption == null ? COMMIT_METRICS : ClientType.valueOf(typeOption.getValue());
+ if (typeOption == null) {
+ throw new IllegalArgumentException("clientType option '" + TYPE_OPTION.getName() + "' cannot be null");
+ }
+ type = ClientType.valueOf(typeOption.getValue());
} catch (Exception ex) {
- type = COMMIT_METRICS;
+ LOGGER.error("Exception while reading clientType argument '{}', process will now exit: ", TYPE_OPTION.getName(), ex);
+ System.exit(2);
+ return;
}
final Thread mainThread = Thread.currentThread();
@@ -227,7 +231,7 @@ void invoke(ClientType clientType) {
try {
LOGGER.info("Starting service.");
- ExecutorService service = ClientServiceFactory.startClientService(_system, clientType, _jobCounter);
+ ExecutorService[] services = ClientServiceFactory.startClientService(_system, clientType, _jobCounter);
LOGGER.info("Service started.");
@@ -242,13 +246,21 @@ void invoke(ClientType clientType) {
}
}
LOGGER.info("Stopping service.");
- service.shutdownNow();
- try {
- if (!service.awaitTermination(60000, TimeUnit.MILLISECONDS)) {
- LOGGER.warn("Shutdown timed out after 60 seconds. Exiting.");
+
+ for (ExecutorService s: services)
+ {
+ s.shutdownNow();
+ try
+ {
+ if (!s.awaitTermination(60000, TimeUnit.MILLISECONDS))
+ {
+ LOGGER.warn("Shutdown timed out after 60 seconds. Exiting.");
+ }
+ } catch (InterruptedException iex)
+ {
+ LOGGER.warn("Forcing shutdown.");
+ break;
}
- } catch (InterruptedException iex) {
- LOGGER.warn("Forcing shutdown.");
}
LOGGER.info("Service stopped.");
} catch (Exception ex) {
diff --git a/ArgusClient/src/main/java/com/salesforce/dva/argus/client/MetricCommitter.java b/ArgusClient/src/main/java/com/salesforce/dva/argus/client/MetricCommitter.java
index d3519c3d9..294aa2802 100644
--- a/ArgusClient/src/main/java/com/salesforce/dva/argus/client/MetricCommitter.java
+++ b/ArgusClient/src/main/java/com/salesforce/dva/argus/client/MetricCommitter.java
@@ -60,8 +60,8 @@ public class MetricCommitter extends AbstractCommitter {
* @param monitorService The monitoring service to use. Cannot be null.
* @param jobCounter The global job counter used to track the number of annotations.
*/
- MetricCommitter(CollectionService colletionService, MonitorService monitorService, AtomicInteger jobCounter) {
- super(colletionService,monitorService, jobCounter);
+ MetricCommitter(CollectionService collectionService, MonitorService monitorService, AtomicInteger jobCounter) {
+ super(collectionService,monitorService, jobCounter);
}
//~ Methods **************************************************************************************************************************************
@@ -95,7 +95,7 @@ public void run() {
LOGGER.info("Error occurred while committing metrics. Reason {}", ex.toString());
}
}
- LOGGER.warn(MessageFormat.format("Metric committer thread interrupted. {} datapoints committed by this thread.", jobCounter.get()));
+ LOGGER.warn("Metric committer thread interrupted. {} datapoints committed by this thread.", jobCounter.get());
collectionService.dispose();
monitorService.dispose();
}
diff --git a/ArgusClient/src/main/java/com/salesforce/dva/argus/client/Refocuser.java b/ArgusClient/src/main/java/com/salesforce/dva/argus/client/Refocuser.java
new file mode 100644
index 000000000..fa8ba90c1
--- /dev/null
+++ b/ArgusClient/src/main/java/com/salesforce/dva/argus/client/Refocuser.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2016, Salesforce.com, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of Salesforce.com nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.salesforce.dva.argus.client;
+
+import com.salesforce.dva.argus.service.RefocusService;
+
+import org.apache.commons.lang.exception.ExceptionUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Forwards batches of refocus notifications.
+ *
+ * @author Ian Keck (ikeck@salesforce.com)
+ */
+class Refocuser implements Runnable {
+
+ //~ Static fields/initializers *******************************************************************************************************************
+
+ private static final long POLL_INTERVAL_MS = 10;
+ private static final Logger LOGGER = LoggerFactory.getLogger(Refocuser.class);
+
+ //~ Instance fields ******************************************************************************************************************************
+
+ private final RefocusService service;
+ private final int timeout;
+
+ //~ Constructors *********************************************************************************************************************************
+
+ /**
+ * Creates a new Alerter object.
+ *
+ * @param service The Refocus service to use.
+ * @param timeout The timeout in milliseconds for a single alert evaluation. Must be a positive number.
+ */
+ Refocuser(RefocusService service, int timeout) {
+ this.service = service;
+ this.timeout = timeout;
+ }
+
+ //~ Methods **************************************************************************************************************************************
+
+ @Override
+ public void run() {
+
+ while (!Thread.currentThread().isInterrupted()) {
+ try {
+ service.forwardNotifications();
+ Thread.sleep(POLL_INTERVAL_MS); // TODO - needed?
+
+ } catch (InterruptedException ex) {
+ LOGGER.info("Execution was interrupted.");
+ Thread.currentThread().interrupt();
+ break;
+ } catch (Throwable ex) {
+ LOGGER.error("Exception in RefocusForwarder: {}", ExceptionUtils.getFullStackTrace(ex));
+ }
+ }
+ service.dispose();
+ }
+}
+/* Copyright (c) 2019, Salesforce.com, Inc. All rights reserved. */
diff --git a/ArgusClient/src/main/java/com/salesforce/dva/argus/client/SchemaCommitter.java b/ArgusClient/src/main/java/com/salesforce/dva/argus/client/SchemaCommitter.java
index 96c720401..ab4d6bc7a 100644
--- a/ArgusClient/src/main/java/com/salesforce/dva/argus/client/SchemaCommitter.java
+++ b/ArgusClient/src/main/java/com/salesforce/dva/argus/client/SchemaCommitter.java
@@ -87,7 +87,7 @@ public void run() {
LOGGER.warn("Error occurred while committing metrics for schema records creation.", ex);
}
}
- LOGGER.warn(MessageFormat.format("Schema committer thread interrupted. {} metrics committed by this thread.", jobCounter.get()));
+ LOGGER.warn("Schema committer thread interrupted. {} metrics committed by this thread.", jobCounter.get());
collectionService.dispose();
monitorService.dispose();
}
diff --git a/ArgusCore/pom.xml b/ArgusCore/pom.xml
index 852a46877..45dd3172e 100644
--- a/ArgusCore/pom.xml
+++ b/ArgusCore/pom.xml
@@ -5,15 +5,18 @@
arguscom.salesforce.argus
- 2.22.3
+ 4-SNAPSHOT..
+
argus-corejar
+ 4.75-SNAPSHOTArgusCore
- Argus core services. Business layer for the Argus Production Monitoring Tool.
+ Argus core services.${project.basedir}/${project.parent.relativePath}
+ 2.1.1
@@ -141,23 +144,24 @@
+
-
- ${project.groupId}
- argus
- ${project.version}
- resources
- zip
- provided
-
-
- ${project.groupId}
- argus
- ${project.version}
- test-resources
- zip
- provided
-
+
+ ${project.groupId}
+ argus
+ ${project.parent.version}
+ resources
+ zip
+ provided
+
+
+ ${project.groupId}
+ argus
+ ${project.parent.version}
+ test-resources
+ zip
+ provided
+ ch.qos.logbacklogback-classic
@@ -169,7 +173,7 @@
org.mockitomockito-core
- 1.10.19
+ 2.27.0test
@@ -178,6 +182,12 @@
1.3test
+
+ com.github.tomakehurst
+ wiremock
+ 2.18.0
+ test
+ junitjunit
@@ -249,6 +259,11 @@
mail1.4.7
+
+ javax.ws.rs
+ javax.ws.rs-api
+ 2.0
+ org.apache.commonscommons-lang3
@@ -256,34 +271,33 @@
org.apache.kafka
- kafka_2.10
- 0.8.2.1
+ kafka_2.11
+ ${kafka.version}
+
+
+ org.slf4j
+ slf4j-log4j12
+
+
+ log4j
+ log4j
+
+ org.apache.kafkakafka-clients
- 0.8.2.1
-
-
- org.scala-lang
- scala-library
- 2.10.5
-
-
- com.101tec
- zkclient
- 0.3
-
-
- org.apache.zookeeper
- zookeeper
+ ${kafka.version}org.slf4jslf4j-log4j12
+
+ log4j
+ log4j
+
- 3.4.10net.sf.jopt-simple
@@ -436,8 +450,8 @@
org.elasticsearch.client
- rest
- 5.5.1
+ elasticsearch-rest-client
+ 6.6.2net.openhft
@@ -445,9 +459,36 @@
0.8
- org.freemarker
- freemarker
- 2.3.28
+ org.freemarker
+ freemarker
+ 2.3.28
+
+
+ io.prometheus.jmx
+ jmx_prometheus_javaagent
+ ${jmx.prometheus.version}
+
+
+ org.jfree
+ jfreechart
+ 1.5.0
+
+
+ io.prometheus.jmx
+ jmx_prometheus_javaagent
+ ${jmx.prometheus.version}
+
+
+ org.powermock
+ powermock-module-junit4
+ 2.0.0
+ test
+
+
+ org.powermock
+ powermock-api-mockito2
+ 2.0.0
+ test
diff --git a/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/AbstractSchemaRecord.java b/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/AbstractSchemaRecord.java
new file mode 100644
index 000000000..84a0f4789
--- /dev/null
+++ b/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/AbstractSchemaRecord.java
@@ -0,0 +1,66 @@
+package com.salesforce.dva.argus.entity;
+
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.net.InetAddress;
+
+public abstract class AbstractSchemaRecord {
+ private static Logger _logger = LoggerFactory.getLogger(AbstractSchemaRecord.class);
+
+ private final static String randomBloomAppend;
+ static {
+ String appendValue;
+ try {
+ appendValue = Integer.toString(Math.abs(InetAddress.getLocalHost().getHostName().hashCode()));
+ } catch (IOException io) {
+ appendValue = "12345";
+ _logger.error("Failed to create randomBloomAppend, using {}. {}", appendValue, io);
+ }
+ randomBloomAppend = appendValue;
+ }
+
+ public abstract String toBloomFilterKey();
+
+ public static String getBloomAppend() {
+ return randomBloomAppend;
+ }
+
+ public static String constructKey(String scope, String metric, String tagk, String tagv, String namespace, String retention) {
+ StringBuilder sb = new StringBuilder(scope);
+ if(!StringUtils.isEmpty(metric)) {
+ sb.append('\0').append(metric);
+ }
+ if(!StringUtils.isEmpty(namespace)) {
+ sb.append('\0').append(namespace);
+ }
+ if(!StringUtils.isEmpty(tagk)) {
+ sb.append('\0').append(tagk);
+ }
+ if(!StringUtils.isEmpty(tagv)) {
+ sb.append('\0').append(tagv);
+ }
+ //there is use case where users simply want to update the retention without touching rest of a metric
+ if(!StringUtils.isEmpty(retention)) {
+ sb.append('\0').append(retention);
+ }
+ sb.append('\0').append(randomBloomAppend);
+
+ return sb.toString();
+ }
+
+ public static String constructKey(Metric metric, String tagk, String tagv) {
+ return constructKey(metric.getScope(),
+ metric.getMetric(),
+ tagk,
+ tagv,
+ metric.getNamespace(),
+ metric.getMetatagsRecord() == null ? null : metric.getMetatagsRecord().getMetatagValue(MetricSchemaRecord.RETENTION_DISCOVERY));
+ }
+
+ public static String constructKey(String scope) {
+ return constructKey(scope, null, null, null, null, null);
+ }
+}
diff --git a/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/Alert.java b/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/Alert.java
index 6ea2129d4..b18bca9ae 100644
--- a/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/Alert.java
+++ b/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/Alert.java
@@ -32,6 +32,7 @@
package com.salesforce.dva.argus.entity;
import static com.salesforce.dva.argus.system.SystemAssert.requireArgument;
+import static com.salesforce.dva.argus.system.SystemAssert.requireArgumentP;
import java.io.IOException;
import java.io.Serializable;
@@ -44,6 +45,8 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Arrays;
+import java.util.stream.Collectors;
import javax.persistence.Basic;
import javax.persistence.CascadeType;
@@ -69,6 +72,8 @@
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Selection;
+import com.salesforce.dva.argus.util.CommonUtils;
+import com.salesforce.dva.argus.util.Cron;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.eclipse.persistence.config.HintValues;
import org.eclipse.persistence.config.QueryHints;
@@ -85,6 +90,9 @@
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.salesforce.dva.argus.service.metric.MetricReader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
/**
* The entity which encapsulates information about a Dashboard.
*
@@ -115,35 +123,36 @@
@NamedQuery(
name = "Alert.findByNameAndOwner",
query =
- "SELECT a FROM Alert a WHERE a.name = :name AND a.owner = :owner AND a.id in (SELECT jpa.id from JPAEntity jpa where jpa.deleted = false)"
+ "SELECT a FROM Alert a WHERE a.name = :name AND a.owner = :owner AND a.id in (SELECT jpa.id from JPAEntity jpa where jpa.deleted = false AND (jpa.createdBy IS NOT NULL AND jpa.modifiedBy IS NOT NULL))"
),
@NamedQuery(
name = "Alert.findByOwner",
- query = "SELECT a FROM Alert a WHERE a.owner = :owner AND a.id in (SELECT jpa.id from JPAEntity jpa where jpa.deleted = false)"
+ query = "SELECT a FROM Alert a WHERE a.owner = :owner AND a.id in (SELECT jpa.id from JPAEntity jpa where jpa.deleted = false AND (jpa.createdBy IS NOT NULL AND jpa.modifiedBy IS NOT NULL))"
),
@NamedQuery(
- name = "Alert.findAll", query = "SELECT a FROM Alert a WHERE a.id in (SELECT jpa.id from JPAEntity jpa where jpa.deleted = false)"
+ name = "Alert.findAll", query = "SELECT a FROM Alert a WHERE a.id in (SELECT jpa.id from JPAEntity jpa where jpa.deleted = false AND (jpa.createdBy IS NOT NULL AND jpa.modifiedBy IS NOT NULL))"
),
@NamedQuery(
name = "Alert.findByStatus",
- query = "SELECT a FROM Alert a where a.enabled= :enabled AND a.id in (SELECT jpa.id from JPAEntity jpa where jpa.deleted = false and TYPE(jpa)= Alert) order by a.id asc"
+ query = "SELECT a FROM Alert a where a.enabled= :enabled AND a.id in (SELECT jpa.id from JPAEntity jpa where jpa.deleted = false and TYPE(jpa)= Alert AND (jpa.createdBy IS NOT NULL AND jpa.modifiedBy IS NOT NULL)) order by a.id asc"
),
@NamedQuery(
name = "Alert.findByRangeAndStatus",
- query = "SELECT a FROM Alert a where a.id BETWEEN :fromId and :toId AND a.enabled= :enabled AND a.id in (SELECT jpa.id from JPAEntity jpa where jpa.deleted = false and TYPE(jpa)= Alert) order by a.id asc"
+ query = "SELECT a FROM Alert a where a.id BETWEEN :fromId and :toId AND a.enabled= :enabled AND a.id in (SELECT jpa.id from JPAEntity jpa where jpa.deleted = false and TYPE(jpa)= Alert AND (jpa.createdBy IS NOT NULL AND jpa.modifiedBy IS NOT NULL)) order by a.id asc"
),
@NamedQuery(
name = "Alert.findIDsByStatus",
- query = "SELECT a.id FROM Alert a where a.enabled= :enabled AND a.id in (SELECT jpa.id from JPAEntity jpa where jpa.deleted = false and TYPE(jpa)= Alert) order by a.id asc"
+ query = "SELECT a.id FROM Alert a where a.enabled= :enabled AND a.id in (SELECT jpa.id from JPAEntity jpa where jpa.deleted = false and TYPE(jpa)= Alert AND (jpa.createdBy IS NOT NULL AND jpa.modifiedBy IS NOT NULL)) order by a.id asc"
),
@NamedQuery(
name = "Alert.findAlertsModifiedAfterDate",
- query = "SELECT a FROM Alert a where a.id in (SELECT jpa.id from JPAEntity jpa where TYPE(jpa)= Alert and jpa.modifiedDate >= :modifiedDate) order by a.id asc"
+ query = "SELECT a FROM Alert a where a.id in (SELECT jpa.id from JPAEntity jpa where TYPE(jpa)= Alert and jpa.modifiedDate >= :modifiedDate AND (jpa.createdBy IS NOT NULL AND jpa.modifiedBy IS NOT NULL)) order by a.id asc"
),
@NamedQuery(
name = "Alert.findByPrefix",
- query = "SELECT a FROM Alert a where a.name LIKE :name AND a.id in (SELECT jpa.id from JPAEntity jpa where jpa.deleted = false)"
- ), @NamedQuery(name = "Alert.setEnabled", query = "UPDATE Alert a SET a.enabled=true WHERE a = :alert"),
+ query = "SELECT a FROM Alert a where a.name LIKE :name AND a.id in (SELECT jpa.id from JPAEntity jpa where jpa.deleted = false AND (jpa.createdBy IS NOT NULL AND jpa.modifiedBy IS NOT NULL))"
+ ),
+ @NamedQuery(name = "Alert.setEnabled", query = "UPDATE Alert a SET a.enabled=true WHERE a = :alert"),
@NamedQuery(name = "Alert.setDisabled", query = "UPDATE Alert a SET a.enabled=false WHERE a = :alert"),
@NamedQuery(name = "Alert.countByStatus", query = "SELECT count(a) from Alert a where a.enabled= :enabled"),
@NamedQuery(
@@ -157,29 +166,29 @@
// Count alert queries
@NamedQuery(
name = "Alert.countByOwner",
- query = "SELECT count(a) FROM Alert a WHERE a.owner = :owner AND a.id in (SELECT jpa.id from JPAEntity jpa where jpa.deleted = false)"
+ query = "SELECT count(a) FROM Alert a WHERE a.owner = :owner AND a.id in (SELECT jpa.id from JPAEntity jpa where jpa.deleted = false AND (jpa.createdBy IS NOT NULL AND jpa.modifiedBy IS NOT NULL))"
),
@NamedQuery(
name = "Alert.countByOwnerWithSearchText",
- query = "SELECT count(a) FROM Alert a WHERE a.owner = :owner AND a.id in (SELECT jpa.id from JPAEntity jpa where jpa.deleted = false) "
+ query = "SELECT count(a) FROM Alert a WHERE a.owner = :owner AND a.id in (SELECT jpa.id from JPAEntity jpa where jpa.deleted = false AND (jpa.createdBy IS NOT NULL AND jpa.modifiedBy IS NOT NULL)) "
+ "AND (FUNCTION('LOWER', a.name) LIKE :searchtext OR FUNCTION('LOWER', a.owner.userName) LIKE :searchtext)"
),
@NamedQuery(
name = "Alert.countSharedAlerts",
- query = "SELECT count(a) from Alert a where a.shared = true AND a.id in (SELECT jpa.id from JPAEntity jpa where jpa.deleted = false)"
+ query = "SELECT count(a) from Alert a where a.shared = true AND a.id in (SELECT jpa.id from JPAEntity jpa where jpa.deleted = false AND (jpa.createdBy IS NOT NULL AND jpa.modifiedBy IS NOT NULL))"
),
@NamedQuery(
name = "Alert.countSharedAlertsWithSearchText",
- query = "SELECT count(a) FROM Alert a WHERE a.shared = true AND a.id IN (SELECT jpa.id FROM JPAEntity jpa WHERE jpa.deleted = false) "
+ query = "SELECT count(a) FROM Alert a WHERE a.shared = true AND a.id IN (SELECT jpa.id FROM JPAEntity jpa WHERE jpa.deleted = false AND (jpa.createdBy IS NOT NULL AND jpa.modifiedBy IS NOT NULL)) "
+ "AND (FUNCTION('LOWER', a.name) LIKE :searchtext OR FUNCTION('LOWER', a.owner.userName) LIKE :searchtext)"
),
@NamedQuery(
name = "Alert.countPrivateAlertsForPrivilegedUser",
- query = "SELECT count(a) from Alert a where a.shared = false AND a.id in (SELECT jpa.id from JPAEntity jpa where jpa.deleted = false)"
+ query = "SELECT count(a) from Alert a where a.shared = false AND a.id in (SELECT jpa.id from JPAEntity jpa where jpa.deleted = false AND (jpa.createdBy IS NOT NULL AND jpa.modifiedBy IS NOT NULL))"
),
@NamedQuery(
name = "Alert.countPrivateAlertsForPrivilegedUserWithSearchText",
- query = "SELECT count(a) from Alert a where a.shared = false AND a.id in (SELECT jpa.id from JPAEntity jpa where jpa.deleted = false) "
+ query = "SELECT count(a) from Alert a where a.shared = false AND a.id in (SELECT jpa.id from JPAEntity jpa where jpa.deleted = false AND (jpa.createdBy IS NOT NULL AND jpa.modifiedBy IS NOT NULL)) "
+ "AND (FUNCTION('LOWER', a.name) LIKE :searchtext OR FUNCTION('LOWER', a.owner.userName) LIKE :searchtext)"
),
}
@@ -205,6 +214,7 @@ public class Alert extends JPAEntity implements Serializable, CronJob {
@Metadata
private boolean enabled = false;
+ @Metadata
private boolean missingDataNotificationEnabled;
@OneToMany(mappedBy = "alert", cascade = CascadeType.ALL, orphanRemoval = true)
@@ -221,6 +231,9 @@ public class Alert extends JPAEntity implements Serializable, CronJob {
@Metadata
private boolean shared;
+ //~ Static Instance fields ***********************************************************************************************************************
+ private static final Logger LOGGER = LoggerFactory.getLogger(Alert.class);
+
// Default values for page limit and page offset
private static int DEFAULT_PAGE_LIMIT = 10;
private static int DEFAULT_PAGE_OFFSET = 0;
@@ -249,9 +262,100 @@ public Alert(PrincipalUser creator, PrincipalUser owner, String name, String exp
setName(name);
setExpression(expression);
setCronEntry(cronEntry);
- setEnabled(false);
setMissingDataNotificationEnabled(false);
- setShared(false);
+ setShared(false);
+ setEnabled(false);
+ }
+
+ /**
+ * Creates an alert object by creating a deep copy of another alert
+ *
+ * @param other The alert to be copied
+ * @param user The owner of the new alert. Cannot be null.
+ * @param alertName The name of the new alert. Cannot be null or empty.
+ *
+ * @throws Exception Throws exception if a problem is encountered while copying props of the original alert.
+ */
+ public Alert(Alert other, String alertName, PrincipalUser user) throws Exception {
+ this(user, user, alertName, other.getExpression(), other.getCronEntry());
+
+ List clonedTriggers = new ArrayList<>();
+ List clonedNotifications = new ArrayList<>();
+ Map triggersCreatedMapByHash = new HashMap<>();
+
+ /*For each existing notification, create new cloned notification.
+ * For each existing trigger in the current notification, create new cloned trigger and add it to cloned notification.
+ **/
+ for (Notification currentNotification : other.getNotifications()) {
+ Notification currentNotificationCloned = new Notification(
+ currentNotification.getName(),
+ this,
+ currentNotification.getNotifierName(),
+ currentNotification.getSubscriptions(),
+ currentNotification.getCooldownPeriod()
+ );
+
+ clonedNotifications.add(currentNotificationCloned);
+
+ CommonUtils.copyProperties(currentNotificationCloned, currentNotification);
+ currentNotificationCloned.setAlert(this);
+ currentNotificationCloned.setCreatedBy(user);
+ currentNotificationCloned.setModifiedBy(user);
+
+ List triggersInCurrentNotification = new ArrayList<>();
+ for (Trigger currentTrigger : currentNotification.getTriggers()) {
+ int currentTriggerHash = currentTrigger.hashCode();
+ if (!triggersCreatedMapByHash.containsKey(currentTriggerHash)) {
+ Trigger currentTriggerCloned = new Trigger(
+ this,
+ currentTrigger.getType(),
+ currentTrigger.getName(),
+ currentTrigger.getThreshold(),
+ currentTrigger.getSecondaryThreshold(),
+ currentTrigger.getInertia()
+ );
+ clonedTriggers.add(currentTriggerCloned);
+ CommonUtils.copyProperties(currentTriggerCloned, currentTrigger);
+ currentTriggerCloned.setCreatedBy(user);
+ currentTriggerCloned.setModifiedBy(user);
+ currentTriggerCloned.setAlert(this);
+ triggersCreatedMapByHash.put(currentTriggerHash, currentTriggerCloned);
+ }
+ triggersInCurrentNotification.add(triggersCreatedMapByHash.get(currentTriggerHash));
+ }
+ currentNotificationCloned.setTriggers(triggersInCurrentNotification);
+ }
+
+ /*
+ * Triggers with no notifications attached
+ * */
+ for (Trigger currentTrigger : other.getTriggers()) {
+ int currentTriggerHash = currentTrigger.hashCode();
+ if (!triggersCreatedMapByHash.containsKey(currentTriggerHash)) {
+ Trigger currentTriggerCloned = new Trigger(
+ this,
+ currentTrigger.getType(),
+ currentTrigger.getName(),
+ currentTrigger.getThreshold(),
+ currentTrigger.getSecondaryThreshold(),
+ currentTrigger.getInertia()
+ );
+ clonedTriggers.add(currentTriggerCloned);
+ CommonUtils.copyProperties(currentTriggerCloned, currentTrigger);
+ currentTriggerCloned.setCreatedBy(user);
+ currentTriggerCloned.setModifiedBy(user);
+ currentTriggerCloned.setAlert(this);
+ triggersCreatedMapByHash.put(currentTriggerHash, currentTriggerCloned);
+ }
+ }
+
+ // NOTE: whenever a new field gets added to an Alert object make sure to update this clone
+ this.setMissingDataNotificationEnabled(other.isMissingDataNotificationEnabled());
+ this.setShared(other.isShared());
+ this.setTriggers(clonedTriggers);
+ this.setNotifications(clonedNotifications);
+ this.setModifiedBy(user);
+ this.setEnabled(other.isEnabled()); // This should be last
}
/** Creates a new Alert object. */
@@ -386,7 +490,7 @@ public static List findByOwnerMeta(EntityManager em, PrincipalUser owner)
whereParams.put(OWNER_KEY, owner);
// Get alerts meta
- return getAlertsMetaPaged(em, null, null, whereParams, null);
+ return getAlertsMetaPaged(em, null, null, whereParams, null, null, null);
} catch (NoResultException ex) {
return new ArrayList<>(0);
}
@@ -405,11 +509,15 @@ public static List findByOwnerMeta(EntityManager em, PrincipalUser owner)
* The starting offset of the result.
* @param searchText
* The text to filter the search results.
- *
+ * @param sortField
+ * The field of the alert that is used for sorting.
+ * @param sortOrder
+ * The order for sorting.
+ *
* @return The list of alerts for the owner.
*/
public static List findByOwnerMetaPaged(EntityManager em, PrincipalUser owner, Integer limit,
- Integer offset, String searchText) {
+ Integer offset, String searchText, String sortField, String sortOrder) {
requireArgument(em != null, "Entity manager can not be null.");
requireArgument(owner != null, "Owner cannot be null");
@@ -430,7 +538,7 @@ public static List findByOwnerMetaPaged(EntityManager em, PrincipalUser o
whereParams.put(OWNER_KEY, owner);
// Get alerts meta
- return getAlertsMetaPaged(em, limit, offset, whereParams, searchText);
+ return getAlertsMetaPaged(em, limit, offset, whereParams, searchText, sortField, sortOrder);
} catch (NoResultException ex) {
return new ArrayList<>(0);
}
@@ -471,7 +579,7 @@ public static List findAllMeta(EntityManager em) {
whereParams.put(DELETED_KEY, false);
// Get alerts meta
- return getAlertsMetaPaged(em, null, null, whereParams, null);
+ return getAlertsMetaPaged(em, null, null, whereParams, null, null, null);
} catch (NoResultException ex) {
return new ArrayList<>(0);
}
@@ -719,7 +827,7 @@ public static List findSharedAlertsMeta(EntityManager em, PrincipalUser o
}
// Get alerts meta
- return getAlertsMetaPaged(em, limit, null, whereParams, null);
+ return getAlertsMetaPaged(em, limit, null, whereParams, null, null, null);
} catch (NoResultException ex) {
return new ArrayList<>(0);
}
@@ -736,10 +844,15 @@ public static List findSharedAlertsMeta(EntityManager em, PrincipalUser o
* The starting offset of the result.
* @param searchText
* The text to filter the search results.
+ * @param sortField
+ * The field of the alert that is used for sorting.
+ * @param sortOrder
+ * The order for sorting.
*
* @return The list of shared alerts with given limit and offset.
*/
- public static List findSharedAlertsMetaPaged(EntityManager em, Integer limit, Integer offset, String searchText) {
+ public static List findSharedAlertsMetaPaged(EntityManager em, Integer limit, Integer offset, String searchText,
+ String sortField, String sortOrder) {
requireArgument(em != null, "Entity manager can not be null.");
if (searchText != null) {
@@ -760,7 +873,7 @@ public static List findSharedAlertsMetaPaged(EntityManager em, Integer li
whereParams.put(SHARED_KEY, true);
// Get alerts meta
- return getAlertsMetaPaged(em, limit, offset, whereParams, searchText);
+ return getAlertsMetaPaged(em, limit, offset, whereParams, searchText, sortField, sortOrder);
} catch (NoResultException ex) {
return new ArrayList<>(0);
}
@@ -775,10 +888,15 @@ public static List findSharedAlertsMetaPaged(EntityManager em, Integer li
* @param offset The starting offset of the result.
* @param searchText
* The text to filter the search results.
- *
+ * @param sortField
+ * The field of the alert that is used for sorting.
+ * @param sortOrder
+ * The order for sorting.
+ *
* @return The list of private alerts' meta with given limit and offset.
*/
- public static List findPrivateAlertsForPrivilegedUserMetaPaged(EntityManager em, PrincipalUser owner, Integer limit, Integer offset, String searchText) {
+ public static List findPrivateAlertsForPrivilegedUserMetaPaged(EntityManager em, PrincipalUser owner, Integer limit, Integer offset, String searchText,
+ String sortField, String sortOrder) {
requireArgument(em != null, "Entity manager can not be null.");
if (searchText != null) {
@@ -804,7 +922,7 @@ public static List findPrivateAlertsForPrivilegedUserMetaPaged(EntityMana
whereParams.put(SHARED_KEY, false);
// Get alerts meta
- return getAlertsMetaPaged(em, limit, offset, whereParams, searchText);
+ return getAlertsMetaPaged(em, limit, offset, whereParams, searchText, sortField, sortOrder);
} catch (NoResultException ex) {
return new ArrayList<>(0);
}
@@ -884,7 +1002,7 @@ public static List findByPrefix(EntityManager em, String prefix) {
* limit and offset.
*/
private static List getAlertsMetaPaged(EntityManager em, Integer limit, Integer offset,
- Map whereParams, String searchText) {
+ Map whereParams, String searchText, String sortField, String sortOrder) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery cq = cb.createTupleQuery();
Root e = cq.from(Alert.class);
@@ -928,9 +1046,32 @@ private static List getAlertsMetaPaged(EntityManager em, Integer limit, I
cq.where(predicates.toArray(new Predicate[predicates.size()]));
}
- // Sort result by alert id
- cq.orderBy(cb.asc(e.get("id")));
+ // By default, do not sort
+ // Sort based or sortField and sortOrder if both are not null
+ if (sortField != null && sortOrder != null){
+ Expression sortColumn;
+
+ switch(SortFieldType.fromName(sortField)) {
+ case OWNER_NAME :
+ sortColumn = e.join("owner").get("userName");
+ break;
+ default :
+ sortColumn = e.get(sortField);
+ break;
+ }
+ switch(SortOrderType.fromName(sortOrder)){
+ case ASC :
+ cq.orderBy(cb.asc(sortColumn));
+ break;
+ case DESC :
+ cq.orderBy(cb.desc(sortColumn));
+ break;
+ default :
+ break;
+ }
+ }
+
TypedQuery query = em.createQuery(cq);
query.setHint("javax.persistence.cache.storeMode", "REFRESH");
query.setHint(QueryHints.REFRESH, HintValues.TRUE);
@@ -949,19 +1090,24 @@ private static List getAlertsMetaPaged(EntityManager em, Integer limit, I
List alerts = new ArrayList<>();
for (Tuple tuple : result) {
-
- Alert a = new Alert(PrincipalUser.class.cast(tuple.get("createdBy")),
- PrincipalUser.class.cast(tuple.get("owner")), String.class.cast(tuple.get("name")),
- String.class.cast(tuple.get("expression")), String.class.cast(tuple.get("cronEntry")));
-
- a.id = BigInteger.class.cast(tuple.get("id"));
- a.enabled = Boolean.class.cast(tuple.get("enabled"));
- a.createdDate = Date.class.cast(tuple.get("createdDate"));
- a.modifiedDate = Date.class.cast(tuple.get("modifiedDate"));
- a.shared = Boolean.class.cast(tuple.get("shared"));
- a.modifiedBy = PrincipalUser.class.cast(tuple.get("modifiedBy"));
-
- alerts.add(a);
+ try {
+ Alert a = new Alert(PrincipalUser.class.cast(tuple.get("createdBy")),
+ PrincipalUser.class.cast(tuple.get("owner")), String.class.cast(tuple.get("name")),
+ String.class.cast(tuple.get("expression")), String.class.cast(tuple.get("cronEntry")));
+
+ a.id = BigInteger.class.cast(tuple.get("id"));
+ a.enabled = Boolean.class.cast(tuple.get("enabled"));
+ a.createdDate = Date.class.cast(tuple.get("createdDate"));
+ a.modifiedDate = Date.class.cast(tuple.get("modifiedDate"));
+ a.shared = Boolean.class.cast(tuple.get("shared"));
+ a.modifiedBy = PrincipalUser.class.cast(tuple.get("modifiedBy"));
+ a.missingDataNotificationEnabled = Boolean.class.cast(tuple.get("missingDataNotificationEnabled"));
+
+ alerts.add(a);
+ } catch (RuntimeException r) {
+ // TODO: Add logging?
+ continue;
+ }
}
// Trim excessive items more then limit in the end
@@ -975,7 +1121,7 @@ private static List getAlertsMetaPaged(EntityManager em, Integer limit, I
private static String _convertSearchTextWildCardForQuery(String searchText) {
return "%" + searchText.toLowerCase().replace("*", "%").replace("?","_") + "%";
}
-
+
/**
* Returns the CRON entry for the alert.
*
@@ -992,6 +1138,7 @@ public String getCronEntry() {
* @param cronEntry The new CRON entry. Cannot be null and must be valid CRON entry syntax.
*/
public void setCronEntry(String cronEntry) {
+ requireArgument(cronEntry != null && !cronEntry.isEmpty(), "CronEntry cannot be null or empty.");
this.cronEntry = cronEntry;
}
@@ -1047,8 +1194,8 @@ public String getExpression() {
*
* @param expression The alert expression. Cannot be null and must be valid metric expression syntax as defined in the MetricService
*/
- public void setExpression(String expression) {
- requireArgument(MetricReader.isValid(expression), "Invalid metric expression " + expression);
+ public void setExpression(String expression) throws RuntimeException {
+ requireArgument(expression != null && !expression.isEmpty(), "Expression cannot be null or empty.");
this.expression = expression;
}
@@ -1159,12 +1306,60 @@ public void setShared(boolean shared) {
this.shared = shared;
}
+
+ /**
+ * Validates all fields of an alert.
+ * @throws RuntimeException
+ */
+ public void validateAlert() throws RuntimeException {
+ requireArgumentP(this.expression, x -> MetricReader.validateExpression(x), "Invalid alert expression: " + this.expression, true);
+ requireArgument(Cron.isCronEntryValid(this.cronEntry), "Invalid cron entry: " + this.cronEntry);
+ requireArgument(this.owner != null, "Owner cannot be null.");
+ requireArgument(this.name != null && !this.name.isEmpty(), "Name cannot be null or empty.");
+ }
+
+ /**
+ * Returns whether the alert is valid
+ */
+ public boolean isValid() {
+ try
+ {
+ this.validateAlert();
+ }
+ catch (RuntimeException e)
+ {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Returns null if the alert is valid or the error message if it is not.
+ */
+ public String validationMessage() {
+ try
+ {
+ this.validateAlert();
+ }
+ catch (RuntimeException e)
+ {
+ return e.getMessage();
+ }
+ return null;
+ }
+
@Override
public int hashCode() {
int hash = 5;
hash = 31 * hash + Objects.hashCode(this.name);
hash = 31 * hash + Objects.hashCode(this.owner);
+ hash = 31 * hash + Objects.hashCode(this.cronEntry);
+ hash = 31 * hash + Objects.hashCode(this.enabled);
+ hash = 31 * hash + Objects.hashCode(this.expression);
+ hash = 31 * hash + Objects.hashCode(this.missingDataNotificationEnabled);
+ hash = 31 * hash + Objects.hashCode(this.shared);
+
return hash;
}
@@ -1179,12 +1374,18 @@ public boolean equals(Object obj) {
final Alert other = (Alert) obj;
- if (!Objects.equals(this.name, other.name)) {
+ if (this.hashCode() != other.hashCode()) {
return false;
}
- if (!Objects.equals(this.owner, other.owner)) {
+
+ if (!CommonUtils.listsAreEquivelent(this.getTriggers(), other.getTriggers())) {
+ return false;
+ }
+
+ if (!CommonUtils.listsAreEquivelent(this.getNotifications(), other.getNotifications())) {
return false;
}
+
return true;
}
@@ -1209,6 +1410,9 @@ public void serialize(Alert alert, JsonGenerator jgen, SerializerProvider provid
jgen.writeBooleanField("enabled", alert.isEnabled());
jgen.writeBooleanField("missingDataNotificationEnabled", alert.isMissingDataNotificationEnabled());
jgen.writeObjectField("owner", alert.getOwner());
+ if(alert.getModifiedDate() != null) {
+ jgen.writeObjectField("modifiedDate", alert.getModifiedDate().getTime());
+ }
jgen.writeArrayFieldStart("triggers");
for(Trigger trigger : alert.getTriggers()) {
@@ -1226,6 +1430,8 @@ public void serialize(Alert alert, JsonGenerator jgen, SerializerProvider provid
}
}
+
+ // NOTE - ian - TODO - create a separate deserializer for testing OR handle missing info here?
public static class Deserializer extends JsonDeserializer {
@Override
@@ -1249,13 +1455,15 @@ public Alert deserialize(JsonParser jp, DeserializationContext ctxt) throws IOEx
alert.setName(name);
String expression = rootNode.get("expression").asText();
+
alert.setExpression(expression);
String cronEntry = rootNode.get("cronEntry").asText();
alert.setCronEntry(cronEntry);
- boolean enabled = rootNode.get("enabled").asBoolean();
- alert.setEnabled(enabled);
+// if(rootNode.get("modifiedDate") != null) {
+// alert.setModifiedDate(Date.from(Instant.ofEpochMilli(rootNode.get("modifiedDate").asLong())));
+// }
boolean missingDataNotificationEnabled = rootNode.get("missingDataNotificationEnabled").asBoolean();
alert.setMissingDataNotificationEnabled(missingDataNotificationEnabled);
@@ -1287,6 +1495,10 @@ public Alert deserialize(JsonParser jp, DeserializationContext ctxt) throws IOEx
}
alert.setNotifications(notifications);
+ // This needs to be last because alerts won't validate unless they are complete.
+ boolean enabled = rootNode.get("enabled").asBoolean();
+ alert.setEnabled(enabled);
+
return alert;
}
@@ -1348,8 +1560,91 @@ public PrincipalUser deserialize(JsonParser jp, DeserializationContext ctxt) thr
return user;
}
-
}
+ //~ Enums **************************************************************************************************************************************
+ public enum SortFieldType {
+
+ OWNER_NAME("ownerName"),
+ NAME("name"),
+ MODIFIED_DATE("modifiedDate"),
+ CREATED_DATE("createdDate");
+
+ private String name_;
+ private SortFieldType(String name){
+ name_ = name;
+ }
+
+ /**
+ * Returns a given sort field type corresponding to the given name.
+ *
+ * @param name The sort field type name
+ *
+ * @return The sort field type
+ */
+ public static SortFieldType fromName(String name) {
+ for (SortFieldType t: SortFieldType.values()) {
+ if (t.getName().equalsIgnoreCase(name)) {
+ return t;
+ }
+ }
+ String errorMessage = "SortFieldType " + name
+ + " does not exist or is not supported. Allowed values are: "
+ + Arrays.asList(SortFieldType.values()).stream().map(t -> t.getName()).collect(Collectors.toList());
+
+ LOGGER.error(errorMessage + "\n");
+ throw new IllegalArgumentException(errorMessage);
+ }
+
+ /**
+ * Return sort field type name.
+ *
+ * @return The sort field type name\
+ *
+ */
+ public String getName(){
+ return name_;
+ }
+ }
+
+
+
+ public enum SortOrderType {
+
+ ASC,
+ DESC;
+
+ /**
+ * Returns a given sort order type corresponding to the given name.
+ *
+ * @param name The sort order type name
+ *
+ * @return The sort order type
+ */
+ public static SortOrderType fromName(String name) {
+ for (SortOrderType t : SortOrderType.values()) {
+ if (t.getName().equalsIgnoreCase(name)) {
+ return t;
+ }
+ }
+ String errorMessage = "SortOrderType " + name
+ + " does not exist or is not supported. Allowed values are: "
+ + Arrays.asList(SortOrderType.values());
+
+ LOGGER.error(errorMessage + "\n");
+ throw new IllegalArgumentException(errorMessage);
+ }
+
+ /**
+ * Return Sort order type name.
+ *
+ * @return The sort order type name
+ *
+ */
+ public String getName(){
+ return this.toString();
+ }
+ }
+
}
/* Copyright (c) 2016, Salesforce.com, Inc. All rights reserved. */
\ No newline at end of file
diff --git a/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/Annotation.java b/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/Annotation.java
index 323b2f904..3c5487d85 100644
--- a/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/Annotation.java
+++ b/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/Annotation.java
@@ -36,6 +36,7 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
+import java.util.TreeMap;
import static com.salesforce.dva.argus.system.SystemAssert.requireArgument;
@@ -88,6 +89,34 @@ protected Annotation() {
//~ Methods **************************************************************************************************************************************
+ /**
+ * Returns the size of the annotation in bytes.
+ *
+ * @return The size in bytes.
+ */
+ public int computeSizeBytes() {
+ int size = computeLength(_source);
+ size += computeLength(_id);
+ size += computeLength(_type);
+ size += computeLength(getScope());
+ size += Long.BYTES; // size of timestamp field
+ for (Map.Entry e : _fields.entrySet()) {
+ size += e.getKey().length();
+ size += e.getValue().length();
+ }
+ for (Map.Entry e : getTags().entrySet()) {
+ size += e.getKey().length();
+ size += e.getValue().length();
+ }
+ size += computeLength(getUid());
+ size += computeLength(getMetric());
+ return size;
+ }
+
+ private int computeLength(String s) {
+ return s != null ? s.length() : 0;
+ }
+
/**
* Returns the source of the annotation.
*
@@ -193,7 +222,7 @@ public boolean equals(Object obj) {
* @param scope The scope of the collection. Cannot be null or empty.
*/
@Override
- protected void setScope(String scope) {
+ public void setScope(String scope) {
requireArgument(scope != null && !scope.trim().isEmpty(), "Scope cannot be null or empty.");
super.setScope(scope);
}
@@ -204,7 +233,7 @@ protected void setScope(String scope) {
* @param metric The metric with which the annotation is associated. If not null, it cannot be empty.
*/
@Override
- protected void setMetric(String metric) {
+ public void setMetric(String metric) {
requireArgument(metric == null || !metric.trim().isEmpty(), "Metric can be null, but if specified, cannot be empty");
super.setMetric(metric);
}
@@ -224,7 +253,7 @@ private void setType(String type) {
*
* @param timestamp THe time stamp for the annotation. Cannot be null.
*/
- private void setTimestamp(Long timestamp) {
+ public void setTimestamp(Long timestamp) {
requireArgument(timestamp != null, "Timestamp cannot be null.");
_timestamp = timestamp;
}
@@ -269,5 +298,10 @@ public String toString() {
return MessageFormat.format(format, params);
}
+
+ public static String getIdentifierFieldsAsString(Annotation annotation) {
+ return new StringBuilder(annotation.getScope()).append(":").append(annotation.getMetric()).append(":")
+ .append(annotation.getTags().toString()).append(":").append(annotation.getType()).append(":").append(annotation.getTimestamp()).toString();
+ }
}
/* Copyright (c) 2016, Salesforce.com, Inc. All rights reserved. */
diff --git a/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/Histogram.java b/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/Histogram.java
new file mode 100644
index 000000000..28f4e2c29
--- /dev/null
+++ b/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/Histogram.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright (c) 2019, Salesforce.com, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of Salesforce.com nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.salesforce.dva.argus.entity;
+
+import static com.salesforce.dva.argus.system.SystemAssert.requireArgument;
+
+import java.io.Serializable;
+import java.text.MessageFormat;
+import java.util.Collections;
+import java.util.Map;
+import java.util.TreeMap;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.salesforce.dva.argus.system.SystemAssert;
+
+/**
+ * Time series histogram entity object. This entity encapsulates all the information needed to represent a time series for a histogram within a single
+ * scope. The following tag names are reserved. Any methods that set tags, which use these reserved tag names, will throw a runtime exception.
+ *
+ * @author Dilip Devaraj (ddevaraj@salesforce.com)
+ */
+@SuppressWarnings("serial")
+public class Histogram extends TSDBEntity implements Serializable {
+
+ //~ Instance fields ******************************************************************************************************************************
+ private String _displayName;
+ private String _units;
+ @JsonSerialize (keyUsing = HistogramBucketSerializer.class)
+ @JsonDeserialize (keyUsing = HistogramBucketDeserializer.class)
+ private Map _buckets;
+ private Long _underflow = 0L;
+ private Long _overflow = 0L;
+ private Long _timestamp;
+
+ //~ Constructors *********************************************************************************************************************************
+
+ /**
+ * Creates a new Histogram object by performing a shallow copy of the given Histogram object.
+ *
+ * @param histogram The histogram object to clone. Cannot be null.
+ */
+ public Histogram(Histogram histogram) {
+ SystemAssert.requireArgument(histogram != null, "Histogram to clone cannot be null.");
+ setScope(histogram.getScope());
+ setMetric(histogram.getMetric());
+ setTags(histogram.getTags());
+ _buckets = new TreeMap<>();
+ setBuckets(histogram.getBuckets());
+ setDisplayName(histogram.getDisplayName());
+ setUnits(histogram.getUnits());
+ }
+
+ /**
+ * Creates a new Histogram object.
+ *
+ * @param scope The reverse dotted name of the collection scope. Cannot be null or empty.
+ * @param metric The name of the metric. Cannot be null or empty.
+ */
+ public Histogram(String scope, String metric) {
+ this();
+ setScope(scope);
+ setMetric(metric);
+ }
+
+ /** Creates a new Histogram object. */
+ protected Histogram() {
+ super(null, null);
+ _buckets = new TreeMap<>();
+ }
+
+ //~ Methods **************************************************************************************************************************************
+
+ @Override
+ public void setScope(String scope) {
+ requireArgument(scope != null && !scope.trim().isEmpty(), "Scope cannot be null or empty.");
+ super.setScope(scope);
+ }
+
+ @Override
+ public void setMetric(String metric) {
+ requireArgument(metric != null && !metric.trim().isEmpty(), "Metric cannot be null or empty.");
+ super.setMetric(metric);
+ }
+
+ /**
+ * Returns the optional overflow for the histogram.
+ *
+ * @return The overflow for the histogram.
+ */
+ public Long getOverflow() {
+ return _overflow;
+ }
+
+ /**
+ * Sets the overflow for the histogram.
+ *
+ * @param overflow The overflow for the histogram
+ */
+ public void setOverflow(Long overflow) {
+ _overflow = overflow;
+ }
+
+ /**
+ * Returns the optional underflow for the histogram.
+ *
+ * @return The underflow for the histogram.
+ */
+ public Long getUnderflow() {
+ return _underflow;
+ }
+
+ /**
+ * Sets the underflow for the histogram.
+ *
+ * @param underflow The underflow for the histogram.
+ */
+ public void setUnderflow(Long underflow) {
+ _underflow = underflow;
+ }
+
+ /**
+ * Returns an unmodifiable map of histogram buckets which is backed by the entity objects internal data.
+ *
+ * @return The map of histogram buckets. Will never be null, but may be empty.
+ */
+ public Map getBuckets() {
+ return Collections.unmodifiableMap(_buckets);
+ }
+
+ /**
+ * Add a new bucket with count to exisitng buckets
+ *
+ * @param lowerBound lower bound of bucket
+ * @param upperBound upper bound of bucket
+ * @param count count within this bucket
+ */
+ public void addBucket(float lowerBound, float upperBound, long count) {
+ _buckets.put(new HistogramBucket(lowerBound, upperBound), count);
+ }
+
+ /**
+ * Deletes the current map of histogram buckets and replaces them with a new map.
+ *
+ * @param buckets The new map of histogram buckets. If null or empty, only the deletion of the current set of histogram buckets is performed.
+ */
+ public void setBuckets(Map buckets) {
+ _buckets.clear();
+ if (buckets != null) {
+ _buckets.putAll(buckets);
+ }
+ }
+
+ /*
+ * Deletes the current map of histogram buckets
+ */
+ public void clearBuckets() {
+ _buckets.clear();
+ }
+
+ /**
+ * Sets the display name for the histogram.
+ *
+ * @param displayName The display name for the histogram. Can be null or empty.
+ */
+ public void setDisplayName(String displayName) {
+ _displayName = displayName;
+ }
+
+ /**
+ * Returns the display name for the histogram.
+ *
+ * @return The display name for the histogram. Can be null or empty.
+ */
+ public String getDisplayName() {
+ return _displayName;
+ }
+
+ /**
+ * Sets the units of the histogram values.
+ *
+ * @param units The units of the histogram values. Can be null or empty.
+ */
+ public void setUnits(String units) {
+ _units = units;
+ }
+
+ /**
+ * Returns the units of the histogram values.
+ *
+ * @return The units of the histogram values. Can be null or empty.
+ */
+ public String getUnits() {
+ return _units;
+ }
+
+ /**
+ * Sets the time stamp at which the histogram exists.
+ *
+ * @param timestamp The time stamp for the histogram. Cannot be null.
+ */
+ public void setTimestamp(Long timestamp) {
+ requireArgument(timestamp != null, "Timestamp cannot be null.");
+ _timestamp = timestamp;
+ }
+
+ /**
+ * Returns the time stamp of the histogram.
+ *
+ * @return The time stamp of the histogram. Will never be null.
+ */
+ public Long getTimestamp() {
+ return _timestamp;
+ }
+
+ @Override
+ public String toString() {
+ Object[] params = {getTimestamp(), getScope(), getMetric(), getTags(), getBuckets(), getUnderflow(), getOverflow() };
+ String format = "timestamp=>{0,number,#}, scope=>{1}, metric=>{2}, tags=>{3}, buckets=>{4}, underflow=>{5}, overflow=>{6}";
+
+ return MessageFormat.format(format, params);
+ }
+
+ /**
+ * To return an identifier string, the format is <scope>:<name>{<tags>}
+ *
+ * @return Returns a metric identifier for the histogram. Will never return null.
+ */
+ @JsonIgnore
+ public String getIdentifier() {
+
+ String tags = "";
+ Map sortedTags = getTags();
+ if(!sortedTags.isEmpty()) {
+ StringBuilder tagListBuffer = new StringBuilder("{");
+ for (String tagKey : sortedTags.keySet()) {
+ tagListBuffer.append(tagKey).append('=').append(sortedTags.get(tagKey)).append(',');
+ }
+
+ tags = tagListBuffer.substring(0, tagListBuffer.length() - 1).concat("}");
+ }
+
+ Object[] params = { getScope(), getMetric(), tags };
+ String format = "{0}:{1}" + "{2}";
+
+ return MessageFormat.format(format, params);
+ }
+
+}
+/* Copyright (c) 2019, Salesforce.com, Inc. All rights reserved. */
\ No newline at end of file
diff --git a/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/HistogramBucket.java b/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/HistogramBucket.java
new file mode 100644
index 000000000..858102b3f
--- /dev/null
+++ b/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/HistogramBucket.java
@@ -0,0 +1,57 @@
+package com.salesforce.dva.argus.entity;
+
+import java.io.Serializable;
+
+import com.fasterxml.jackson.annotation.JsonValue;
+
+/**
+ * HistogramBucket object that encompasses the lower, upper bound of this histogram bucket.
+ *
+ * @author Dilip Devaraj (ddevaraj@salesforce.com)
+ */
+public class HistogramBucket implements Serializable, Comparable {
+ private static final long serialVersionUID = 1L;
+ private float lowerBound;
+ private float upperBound;
+
+ public HistogramBucket(int lowerBound, int upperBound) {
+ this.lowerBound = lowerBound;
+ this.upperBound = upperBound;
+ }
+
+ public HistogramBucket(float lowerBound, float upperBound) {
+ this.lowerBound = lowerBound;
+ this.upperBound = upperBound;
+ }
+
+ public HistogramBucket(String value) {
+ String[] bounds = value.split(",");
+ this.lowerBound = Float.parseFloat(bounds[0].trim());
+ this.upperBound = Float.parseFloat(bounds[1].trim());
+ }
+
+ public float getLowerBound() {
+ return lowerBound;
+ }
+
+ public float getUpperBound() {
+ return upperBound;
+ }
+
+ @Override
+ public int compareTo(HistogramBucket that) {
+ if(this.equals(that)){
+ return 0;
+ } else {
+ int lowerBoundCompare = Float.compare(this.lowerBound, that.lowerBound);
+ if(lowerBoundCompare !=0) return lowerBoundCompare;
+ else return Float.compare(this.upperBound, that.upperBound);
+ }
+ }
+
+ @Override
+ @JsonValue
+ public String toString() {
+ return lowerBound + "," + upperBound;
+ }
+}
\ No newline at end of file
diff --git a/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/HistogramBucketDeserializer.java b/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/HistogramBucketDeserializer.java
new file mode 100644
index 000000000..69e58ec0a
--- /dev/null
+++ b/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/HistogramBucketDeserializer.java
@@ -0,0 +1,21 @@
+package com.salesforce.dva.argus.entity;
+
+import java.io.IOException;
+
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.KeyDeserializer;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * HistogramBucket Deserializer
+ *
+ * @author Dilip Devaraj (ddevaraj@salesforce.com)
+ */
+public class HistogramBucketDeserializer extends KeyDeserializer{
+ private static final ObjectMapper mapper = new ObjectMapper();
+
+ @Override
+ public HistogramBucket deserializeKey(String key, DeserializationContext ctxt) throws IOException {
+ return mapper.readValue(key, HistogramBucket.class);
+ }
+}
\ No newline at end of file
diff --git a/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/HistogramBucketSerializer.java b/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/HistogramBucketSerializer.java
new file mode 100644
index 000000000..b217e15c9
--- /dev/null
+++ b/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/HistogramBucketSerializer.java
@@ -0,0 +1,25 @@
+package com.salesforce.dva.argus.entity;
+
+import java.io.IOException;
+import java.io.StringWriter;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializerProvider;
+
+/**
+ * HistogramBucket Serializer
+ *
+ * @author Dilip Devaraj (ddevaraj@salesforce.com)
+ */
+public class HistogramBucketSerializer extends JsonSerializer {
+ private static final ObjectMapper mapper = new ObjectMapper();
+
+ @Override
+ public void serialize(HistogramBucket value, JsonGenerator jgen, SerializerProvider serializers) throws IOException {
+ StringWriter writer = new StringWriter();
+ mapper.writeValue(writer, value);
+ jgen.writeFieldName(writer.toString());
+ }
+}
diff --git a/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/ImagePoints.java b/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/ImagePoints.java
new file mode 100644
index 000000000..6f5852141
--- /dev/null
+++ b/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/ImagePoints.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2016, Salesforce.com, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of Salesforce.com nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.salesforce.dva.argus.entity;
+
+
+import org.apache.commons.lang.StringUtils;
+import java.io.Serializable;
+
+/**
+ * This class represents the points in XY graph as well as the properties related to that
+ */
+public class ImagePoints implements Cloneable, Serializable {
+
+ /** The first value. */
+ private double firstPoint;
+
+ /** The second value. */
+ private double secondPoint;
+
+ /** The label */
+ private String label = StringUtils.EMPTY;
+
+ /** The Color */
+ private ImageProperties.ImageColors color = ImageProperties.DEFAULT_COLOR;
+
+ /**
+ *
+ * @param firstPoint Represents first point in the XY Axis
+ * @param secondPoint Represents second point in the XY Axis
+ * @param label Represents label associated with the points
+ */
+ public ImagePoints(double firstPoint, double secondPoint, String label) {
+ this.firstPoint = firstPoint;
+ this.secondPoint = secondPoint;
+ this.label = label;
+ }
+
+ /**
+ *
+ * @param firstPoint Represents first point in the XY Axis
+ * @param secondPoint Represents second point in the XY Axis
+ * @param color Represents color associated with the points
+ */
+ public ImagePoints(double firstPoint, double secondPoint, ImageProperties.ImageColors color) {
+ this.firstPoint = firstPoint;
+ this.secondPoint = secondPoint;
+ this.color = color;
+ }
+
+ /**
+ *
+ * @param firstPoint Represents first point in the XY Axis
+ * @param secondPoint Represents second point in the XY Axis
+ * @param label Represents label associated with the points
+ * @param color Represents color associated with the points
+ */
+ public ImagePoints(double firstPoint, double secondPoint, String label, ImageProperties.ImageColors color) {
+ this.firstPoint = firstPoint;
+ this.secondPoint = secondPoint;
+ this.label = label;
+ this.color = color;
+ }
+
+ /**
+ * Gets first point in the XY Axis
+ * @return first point in the XY Axis
+ */
+ public double getFirstPoint() {
+ return firstPoint;
+ }
+
+ /**
+ * Sets first point in the XY Axis
+ * @param firstPoint
+ */
+ public void setFirstPoint(double firstPoint) {
+ this.firstPoint = firstPoint;
+ }
+
+ /**
+ * Gets second point in the XY Axis
+ * @return second point in the XY Axis
+ */
+ public double getSecondPoint() {
+ return secondPoint;
+ }
+
+ /**
+ * Sets second point in the XY Axis
+ * @param secondPoint
+ */
+ public void setSecondPoint(double secondPoint) {
+ this.secondPoint = secondPoint;
+ }
+
+ /**
+ * Gets the label associated with the points
+ * @return Label is returned
+ */
+ public String getLabel() {
+ return label;
+ }
+
+ /**
+ * Sets the label associated with the points
+ * @param label
+ */
+ public void setLabel(String label) {
+ this.label = label;
+ }
+
+ /**
+ * Gets the color associated with the points
+ * @return Color associated
+ */
+ public ImageProperties.ImageColors getColor() {
+ return color;
+ }
+
+ /**
+ * Sets the color associated with the points
+ * @param color Color associated
+ */
+ public void setColor(ImageProperties.ImageColors color) {
+ this.color = color;
+ }
+}
diff --git a/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/ImageProperties.java b/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/ImageProperties.java
new file mode 100644
index 000000000..453ad96b0
--- /dev/null
+++ b/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/ImageProperties.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (c) 2016, Salesforce.com, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of Salesforce.com nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.salesforce.dva.argus.entity;
+
+import org.apache.commons.lang.StringUtils;
+import java.awt.Color;
+import java.util.List;
+
+public class ImageProperties {
+
+
+ private static final String DEFAULT_IMAGE_XAXIS_LABEL="Time";
+ private static final String DEFAULT_IMAGE_YAXIS_LABEL="Value";
+ private static final String DEFAULT_IMAGE_CHART_NAME = StringUtils.EMPTY;
+ private static final int DEFAULT_IMAGE_WIDTH = 1100;
+ private static final int DEFAULT_IMAGE_HEIGHT = 550;
+ public static final ImageColors DEFAULT_COLOR = ImageColors.VERY_LIGHT_BLUE;
+
+ private int imageWidth = DEFAULT_IMAGE_WIDTH;
+ private int imageHeight = DEFAULT_IMAGE_HEIGHT;
+ private String chartName = DEFAULT_IMAGE_CHART_NAME;
+ private String xAxisName = DEFAULT_IMAGE_XAXIS_LABEL;
+ private String yAxisName = DEFAULT_IMAGE_YAXIS_LABEL;
+ private List shadeXAxisArea;
+ private List shadeYAxisArea;
+ private List labelPoints;
+
+
+ /**
+ * Gets the Image Width. Default value is 1100
+ * @return Returns the Image width
+ */
+ public int getImageWidth() {
+ return imageWidth;
+ }
+
+
+ /**
+ * Sets the Image Width of the JPG Image
+ * @param imageWidth imageWidth value
+ */
+ public void setImageWidth(int imageWidth) {
+ this.imageWidth = imageWidth;
+ }
+
+
+ /**
+ * Gets the Image Height. Default value is 550
+ * @return Returns the Image Height
+ */
+ public int getImageHeight() {
+ return imageHeight;
+ }
+
+ /**
+ * Sets the Image Height of the JPG Image
+ * @param imageHeight imageHeight Value
+ */
+ public void setImageHeight(int imageHeight) {
+ this.imageHeight = imageHeight;
+ }
+
+ /**
+ * Gets the Chart Name of the Image.By default the chart name is empty
+ * @return Returns the chart name
+ */
+ public String getChartName() {
+ return chartName;
+ }
+
+ /**
+ * Sets the Chart Name of the Image.
+ * @param chartName Chart Name value
+ */
+ public void setChartName(String chartName) {
+ this.chartName = chartName;
+ }
+
+ /**
+ * Gets the X-Axis Name of the Image. By default the value is "Time"
+ * @return Returns the X-Axis Name
+ */
+ public String getxAxisName() {
+ return xAxisName;
+ }
+
+ /**
+ * Sets the X-Axis Name of the Image
+ * @param xAxisName X-Axis Name
+ */
+ public void setxAxisName(String xAxisName) {
+ this.xAxisName = xAxisName;
+ }
+
+ /**
+ * Gets the Y-Axis Name of the Image. By default the value is "Value"
+ * @return Returns the Y-Axis Name
+ */
+ public String getyAxisName() {
+ return yAxisName;
+ }
+
+ /**
+ * Sets the Y-Axis Name of the Image
+ * @param yAxisName Y-Axis Name
+ */
+ public void setyAxisName(String yAxisName) {
+ this.yAxisName = yAxisName;
+ }
+
+ /**
+ * Gets information related to List of ImagePoints that are used to shade the area parallel to X-Axis
+ * @return List of ImagePoints to Shade X-Axis
+ */
+ public List getShadeXAxisArea() {
+ return shadeXAxisArea;
+ }
+
+ /**
+ * Set the Information that is required to shade the area parallel to X-Axis.If y1==y2 then straight line will be drawn
+ * @param shadeXAxisArea List of ImagePoints with each ImagePoints represent y1,y2,label,Color
+ */
+ public void setShadeXAxisArea(List shadeXAxisArea) {
+ this.shadeXAxisArea = shadeXAxisArea;
+ }
+
+ /**
+ * Gets information related to List of ImagePoints that are used to shade the area parallel to Y-Axis
+ * @return List of ImagePoints to Shade Y-Axis
+ */
+ public List getShadeYAxisArea() {
+ return shadeYAxisArea;
+ }
+
+ /**
+ * Set the Information that is required to shade the area parallel to Y-Axis.If x1==x2 then straight line will be drawn
+ * @param shadeYAxisArea List of ImagePoints with each ImagePoints represent x1,x2,label,Color
+ */
+ public void setShadeYAxisArea(List shadeYAxisArea) {
+ this.shadeYAxisArea = shadeYAxisArea;
+ }
+
+ /**
+ * Gets List of ImagePoints to represent labels in XY axis
+ * @return List of ImagePoints to plot labels
+ */
+ public List getLabelPoints() {
+ return labelPoints;
+ }
+
+ /**
+ * Set the information that is required to label points in XY Axis
+ * @param labelPoints List of ImagePoints with each ImagePoints represent x1,y1,label,Color
+ */
+ public void setLabelPoints(List labelPoints) {
+ this.labelPoints = labelPoints;
+ }
+
+ public enum ImageColors {
+
+ VERY_DARK_RED(new Color(0x80, 0x00, 0x00)),
+ DARK_RED(new Color(0xc0, 0x00, 0x00)),
+ LIGHT_RED(new Color(0xFF, 0x40, 0x40)),
+ VERY_LIGHT_RED(new Color(0xFF, 0x80, 0x80)),
+ VERY_DARK_YELLOW(new Color(0x80, 0x80, 0x00)),
+ DARK_YELLOW(new Color(0xC0, 0xC0, 0x00)),
+ LIGHT_YELLOW(new Color(0xFF, 0xFF, 0x40)),
+ VERY_LIGHT_YELLOW(new Color(0xFF, 0xFF, 0x80)),
+ VERY_DARK_GREEN(new Color(0x00, 0x80, 0x00)),
+ DARK_GREEN(new Color(0x00, 0xC0, 0x00)),
+ LIGHT_GREEN(new Color(0x40, 0xFF, 0x40)),
+ VERY_LIGHT_GREEN(new Color(0x80, 0xFF, 0x80)),
+ VERY_DARK_CYAN(new Color(0x00, 0x80, 0x80)),
+ DARK_CYAN(new Color(0x00, 0xC0, 0xC0)),
+ LIGHT_CYAN(new Color(0x40, 0xFF, 0xFF)),
+ VERY_LIGHT_CYAN(new Color(0x80, 0xFF, 0xFF)),
+ VERY_DARK_BLUE(new Color(0x00, 0x00, 0x80)),
+ DARK_BLUE(new Color(0x00, 0x00, 0xC0)),
+ LIGHT_BLUE(new Color(0x40, 0x40, 0xFF)),
+ VERY_LIGHT_BLUE(new Color(0x80, 0x80, 0xFF)),
+ VERY_DARK_MAGENTA(new Color(0x80, 0x00, 0x80)),
+ DARK_MAGENTA(new Color(0xC0, 0x00, 0xC0)),
+ LIGHT_MAGENTA(new Color(0xFF, 0x40, 0xFF)),
+ VERY_LIGHT_MAGENTA(new Color(0xFF, 0x80, 0xFF)),
+ VERY_LIGHT_PINK(new Color(255, 230, 230));
+
+
+ private Color color;
+
+ ImageColors(Color color) {
+ this.setColor(color);
+ }
+
+ public Color getColor() {
+ return color;
+ }
+
+ public void setColor(Color color) {
+ this.color = color;
+ }
+ }
+}
\ No newline at end of file
diff --git a/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/JPAEntity.java b/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/JPAEntity.java
index 929cddcc7..082244838 100644
--- a/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/JPAEntity.java
+++ b/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/JPAEntity.java
@@ -186,6 +186,7 @@ public static E findByPrimaryKey(EntityManager em, BigI
TypedQuery query = em.createNamedQuery("JPAEntity.findByPrimaryKey", type);
query.setHint("javax.persistence.cache.storeMode", "REFRESH");
+
try {
query.setParameter("id", id);
query.setParameter("deleted", false);
@@ -213,6 +214,7 @@ public static List findByPrimaryKeys(EntityManager e
TypedQuery query = em.createNamedQuery("JPAEntity.findByPrimaryKeys", type);
query.setHint("javax.persistence.cache.storeMode", "REFRESH");
+
try {
query.setParameter("ids", ids);
query.setParameter("deleted", false);
@@ -239,6 +241,7 @@ public static List findEntitiesMarkedForDeletion(Ent
TypedQuery query = em.createNamedQuery("JPAEntity.findByDeleteMarker", type);
query.setHint("javax.persistence.cache.storeMode", "REFRESH");
+
try {
query.setParameter("deleted", true);
if(limit > 0) {
diff --git a/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/MetatagsRecord.java b/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/MetatagsRecord.java
index ebd6ffbe9..44aa14aa3 100644
--- a/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/MetatagsRecord.java
+++ b/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/MetatagsRecord.java
@@ -31,12 +31,12 @@
package com.salesforce.dva.argus.entity;
+import com.salesforce.dva.argus.entity.TSDBEntity.ReservedField;
+
import java.util.Collections;
-import java.util.Map;
import java.util.HashMap;
-import java.util.Map.Entry;
-import java.util.TreeMap;
-import com.salesforce.dva.argus.entity.TSDBEntity.ReservedField;
+import java.util.Map;
+import java.util.Objects;
/**
@@ -52,7 +52,7 @@
* @author Kunal Nawale (knawale@salesforce.com)
*/
-public class MetatagsRecord {
+public class MetatagsRecord extends AbstractSchemaRecord {
private Map _metatags = new HashMap<>(0);
private String _key = null;
@@ -140,4 +140,29 @@ public String getMetatagValue(String metatagKey) {
public String removeMetatag(String metatagKey) {
return _metatags.remove(metatagKey);
}
+
+ @Override
+ public String toBloomFilterKey() {
+ return getKey();
+ }
+
+ @Override
+ public int hashCode() {
+ return _key.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ MetatagsRecord other = (MetatagsRecord) obj;
+ return Objects.equals(_key, other._key) && Objects.equals(_metatags, other._metatags);
+ }
}
diff --git a/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/Metric.java b/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/Metric.java
index a5783be6a..43b1e0a0b 100644
--- a/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/Metric.java
+++ b/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/Metric.java
@@ -32,15 +32,16 @@
package com.salesforce.dva.argus.entity;
import com.fasterxml.jackson.annotation.JsonIgnore;
-import com.google.common.base.Objects;
import com.salesforce.dva.argus.service.tsdb.MetricQuery;
import com.salesforce.dva.argus.system.SystemAssert;
import java.io.Serializable;
import java.text.MessageFormat;
import java.util.Collections;
+import java.util.Comparator;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.SortedMap;
import java.util.TreeMap;
import static com.salesforce.dva.argus.system.SystemAssert.requireArgument;
@@ -58,16 +59,21 @@
* @author Tom Valine (tvaline@salesforce.com), Bhinav Sura (bhinav.sura@salesforce.com)
*/
@SuppressWarnings("serial")
-public class Metric extends TSDBEntity implements Serializable {
+public class Metric extends TSDBEntity implements Serializable, Comparable {
+
+ private static final Comparator METRIC_COMPARATOR = Comparator
+ .comparing((Metric m) -> m.getScope().toLowerCase())
+ .thenComparing(m -> m.getMetric().toLowerCase())
+ .thenComparing(m -> m.getTags().toString().toLowerCase());
//~ Instance fields ******************************************************************************************************************************
private String _namespace;
private String _displayName;
private String _units;
- private final Map _datapoints;
+ private final SortedMap _datapoints;
private MetricQuery _query;
- private MetatagsRecord _metatagsRecord = null;
+ private MetatagsRecord _metatagsRecord = null;
//~ Constructors *********************************************************************************************************************************
@@ -146,7 +152,7 @@ public void setNamespace(String namespace) {
* @return The map of time series data points. Will never be null, but may be empty.
*/
public Map getDatapoints() {
- return Collections.unmodifiableMap(_datapoints);
+ return Collections.unmodifiableSortedMap(_datapoints);
}
/**
@@ -176,6 +182,17 @@ public void addDatapoints(Map datapoints) {
}
}
+ /**
+ * Adds the current set of data points to the current map.
+ *
+ * @param time, value A single point to add to timeseries.
+ */
+ public void addDatapoint(Long time, Double value) {
+ if (time != null && value != null) {
+ _datapoints.put(time, value);
+ }
+ }
+
/**
* If current set already has a value at that timestamp then sums up the datapoint value for that timestamp at coinciding cutoff boundary,
@@ -345,8 +362,7 @@ public String getIdentifier() {
String tags = "";
- Map sortedTags = new TreeMap<>();
- sortedTags.putAll(getTags());
+ Map sortedTags = getTags();
if(!sortedTags.isEmpty()) {
StringBuilder tagListBuffer = new StringBuilder("{");
for (String tagKey : sortedTags.keySet()) {
@@ -375,10 +391,25 @@ public MetatagsRecord getMetatagsRecord() {
/**
* Replaces the metatags for a metric. MetatagsRecord cannot use any of the reserved tag names.
*
- * @param metatags The new metatags for the metric.
+ * @param metatagsRec The new metatags for the metric.
*/
public void setMetatagsRecord(MetatagsRecord metatagsRec) {
_metatagsRecord = metatagsRec;
}
+
+ @Override
+ public int compareTo(Metric m) {
+ return METRIC_COMPARATOR.compare(this, m);
+ }
+
+ /**
+ *
+ * @return number of datapoints present
+ */
+
+ @JsonIgnore
+ public int getNumOfDatapoints() {
+ return (_datapoints == null) ? 0: _datapoints.size();
+ }
}
/* Copyright (c) 2016, Salesforce.com, Inc. All rights reserved. */
diff --git a/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/MetricSchemaRecord.java b/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/MetricSchemaRecord.java
index c4b82f894..374af242a 100644
--- a/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/MetricSchemaRecord.java
+++ b/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/MetricSchemaRecord.java
@@ -44,11 +44,11 @@
*
* @author Tom Valine (tvaline@salesforce.com)
*/
-public class MetricSchemaRecord {
+public class MetricSchemaRecord extends AbstractSchemaRecord {
public static final String RETENTION_DISCOVERY = "_retention_discovery_";
public static final String EXPIRATION_TS = "ets"; //expiration timestamp
- public static final int DEFAULT_RETENTION_DISCOVERY_DAYS = 45;
+ public static final int DEFAULT_RETENTION_DISCOVERY_DAYS = 52;
public static final int MAX_RETENTION_DISCOVERY_DAYS = 120;
//~ Instance fields ******************************************************************************************************************************
@@ -366,5 +366,9 @@ public String getStringValueForType(SchemaService.RecordType type) {
}
}
+ @Override
+ public String toBloomFilterKey() {
+ return constructKey(scope, metric, tagKey, tagValue, namespace, retentionDiscovery == null ? null :retentionDiscovery.toString());
+ }
}
/* Copyright (c) 2016, Salesforce.com, Inc. All rights reserved. */
diff --git a/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/MetricSchemaRecordQuery.java b/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/MetricSchemaRecordQuery.java
index f3e584a29..6c4e6dab0 100644
--- a/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/MetricSchemaRecordQuery.java
+++ b/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/MetricSchemaRecordQuery.java
@@ -301,7 +301,10 @@ public MetricSchemaRecordQueryBuilder tagValue(String tagValue) {
this.tagValue = tagValue;
return this;
}
-
+
+ /**
+ * @param limit Maximum amount of hits to return. Set to 0 for unbounded max / unlimited results.
+ */
public MetricSchemaRecordQueryBuilder limit(int limit) {
this.limit = limit;
return this;
diff --git a/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/Notification.java b/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/Notification.java
index 41247e159..6beb495fc 100644
--- a/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/Notification.java
+++ b/ArgusCore/src/main/java/com/salesforce/dva/argus/entity/Notification.java
@@ -43,34 +43,25 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import javax.persistence.Basic;
-import javax.persistence.Column;
-import javax.persistence.ElementCollection;
-import javax.persistence.Entity;
-import javax.persistence.EntityManager;
-import javax.persistence.FetchType;
-import javax.persistence.JoinColumn;
-import javax.persistence.JoinTable;
-import javax.persistence.Lob;
-import javax.persistence.ManyToMany;
-import javax.persistence.ManyToOne;
-import javax.persistence.NoResultException;
-import javax.persistence.Query;
-import javax.persistence.Table;
-import javax.persistence.UniqueConstraint;
+import javax.persistence.*;
import com.fasterxml.jackson.core.JsonGenerator;
-import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.salesforce.dva.argus.service.AlertService;
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import static com.salesforce.dva.argus.system.SystemAssert.requireArgument;
@@ -78,164 +69,176 @@
* Encapsulates information about an alert notification. When a condition is triggered, it sends one or more notifications. The interval over which
* the trigger conditions are evaluated is the entire interval specified by the alert expression.
*
- * @author Tom Valine (tvaline@salesforce.com), Raj Sarkapally(rsarkapally@salesforce.com)
+ * @author Tom Valine (tvaline@salesforce.com), Raj Sarkapally(rsarkapally@salesforce.com)
*/
@SuppressWarnings("serial")
@Entity
-@Table(name = "NOTIFICATION", uniqueConstraints = @UniqueConstraint(columnNames = { "name", "alert_id" }))
+@Table(name = "NOTIFICATION", uniqueConstraints = @UniqueConstraint(columnNames = {"name", "alert_id"}))
public class Notification extends JPAEntity implements Serializable {
-
- public static class Serializer extends JsonSerializer {
-
- @Override
- public void serialize(Notification notification, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
-
- jgen.writeStartObject();
-
- jgen.writeStringField("id", notification.getId().toString());
- if(notification.getCreatedDate()!=null) {
- jgen.writeNumberField("createdDate", notification.getCreatedDate().getTime());
- }
-
- if(notification.getModifiedDate()!=null) {
- jgen.writeNumberField("modifiedDate", notification.getModifiedDate().getTime());
- }
-
- jgen.writeStringField("name", notification.getName());
- jgen.writeStringField("notifier", notification.getNotifierName());
- jgen.writeNumberField("cooldownPeriod", notification.getCooldownPeriod());
- jgen.writeBooleanField("srActionable", notification.getSRActionable());
- jgen.writeNumberField("severityLevel", notification.getSeverityLevel());
-
- if(notification.getCustomText() != null) {
- jgen.writeStringField("customText", notification.getCustomText());
- }
-
- jgen.writeArrayFieldStart("subscriptions");
- for(String subscription : notification.getSubscriptions()) {
- jgen.writeString(subscription);
- }
- jgen.writeEndArray();
-
- jgen.writeArrayFieldStart("metricsToAnnotate");
- for(String metricToAnnotate : notification.getMetricsToAnnotate()) {
- jgen.writeString(metricToAnnotate);
- }
- jgen.writeEndArray();
-
- jgen.writeArrayFieldStart("triggers");
- for(Trigger trigger : notification.getTriggers()) {
- jgen.writeString(trigger.getId().toString());
- }
- jgen.writeEndArray();
-
- // Getting these values requires a lot of queries to rdbms at runtime, and so these are excluded for now
- // as the current usecases do not need these values to be serialized
- //jgen.writeObjectField("cooldownExpirationByTriggerAndMetric", notification.getCooldownExpirationMap());
- //jgen.writeObjectField("activeStatusByTriggerAndMetric", notification.getActiveStatusMap());
-
- jgen.writeEndObject();
-
- }
-
- }
-
- public static class Deserializer extends JsonDeserializer {
-
- @Override
- public Notification deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
-
- Notification notification = new Notification();
-
- JsonNode rootNode = jp.getCodec().readTree(jp);
-
- BigInteger id = new BigInteger(rootNode.get("id").asText());
- notification.id = id;
-
- if(rootNode.get("modifiedDate")!=null) {
- notification.setModifiedDate(Date.from(Instant.ofEpochMilli(rootNode.get("modifiedDate").asLong())));
- }
-
- if(rootNode.get("createdDate")!=null) {
- notification.createdDate = Date.from(Instant.ofEpochMilli(rootNode.get("createdDate").asLong()));
- }
-
- String name = rootNode.get("name").asText();
- notification.setName(name);
-
- String notifierName = rootNode.get("notifier").asText();
- notification.setNotifierName(notifierName);
-
- long cooldownPeriod = rootNode.get("cooldownPeriod").asLong();
- notification.setCooldownPeriod(cooldownPeriod);
-
- boolean srActionable = rootNode.get("srActionable").asBoolean();
- notification.setSRActionable(srActionable);
-
- int severity = rootNode.get("severityLevel").asInt();
- notification.setSeverityLevel(severity);
-
- if(rootNode.get("customText") != null) {
- notification.setCustomText(rootNode.get("customText").asText());
- }
-
- List subscriptions = new ArrayList<>();
- JsonNode subscriptionsArrayNode = rootNode.get("subscriptions");
- if(subscriptionsArrayNode.isArray()) {
- for(JsonNode subscriptionNode : subscriptionsArrayNode) {
- subscriptions.add(subscriptionNode.asText());
- }
- }
- notification.setSubscriptions(subscriptions);
-
- List metricsToAnnotate = new ArrayList<>();
- JsonNode metricsToAnnotateArrayNode = rootNode.get("metricsToAnnotate");
- if(metricsToAnnotateArrayNode.isArray()) {
- for(JsonNode metricToAnnotateNode : metricsToAnnotateArrayNode) {
- metricsToAnnotate.add(metricToAnnotateNode.asText());
- }
- }
- notification.setMetricsToAnnotate(metricsToAnnotate);
-
- List triggers = new ArrayList<>();
- JsonNode triggersArrayNode = rootNode.get("triggers");
- if(triggersArrayNode.isArray()) {
- for(JsonNode triggerNode : triggersArrayNode) {
- BigInteger triggerId = new BigInteger(triggerNode.asText());
- Trigger trigger = new Trigger();
- trigger.id = triggerId;
- triggers.add(trigger);
- }
- }
- notification.setTriggers(triggers);
-
- // Commenting this part out as these fields are not currently serialized
- /*Map activeStatusByTriggerAndMetric = new HashMap<>();
- JsonNode activeStatusByTriggerAndMetricNode = rootNode.get("activeStatusByTriggerAndMetric");
- if(activeStatusByTriggerAndMetricNode.isObject()) {
- Iterator> fieldsIter = activeStatusByTriggerAndMetricNode.fields();
- while(fieldsIter.hasNext()) {
- Entry field = fieldsIter.next();
- activeStatusByTriggerAndMetric.put(field.getKey(), field.getValue().asBoolean());
- }
- }
- notification.activeStatusByTriggerAndMetric = activeStatusByTriggerAndMetric;
-
- Map cooldownExpirationByTriggerAndMetric = new HashMap<>();
- JsonNode cooldownExpirationByTriggerAndMetricNode = rootNode.get("cooldownExpirationByTriggerAndMetric");
- if(cooldownExpirationByTriggerAndMetricNode.isObject()) {
- Iterator> fieldsIter = cooldownExpirationByTriggerAndMetricNode.fields();
- while(fieldsIter.hasNext()) {
- Entry field = fieldsIter.next();
- cooldownExpirationByTriggerAndMetric.put(field.getKey(), field.getValue().asLong());
- }
- }
- notification.cooldownExpirationByTriggerAndMetric = cooldownExpirationByTriggerAndMetric;*/
-
- return notification;
- }
-
- }
+
+ public static class Serializer extends JsonSerializer {
+
+ @Override
+ public void serialize(Notification notification, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
+
+ jgen.writeStartObject();
+
+ jgen.writeStringField("id", notification.getId().toString());
+ if (notification.getCreatedDate() != null) {
+ jgen.writeNumberField("createdDate", notification.getCreatedDate().getTime());
+ }
+
+ if (notification.getModifiedDate() != null) {
+ jgen.writeNumberField("modifiedDate", notification.getModifiedDate().getTime());
+ }
+
+ jgen.writeStringField("name", notification.getName());
+ jgen.writeStringField("notifier", notification.getNotifierName());
+ jgen.writeNumberField("cooldownPeriod", notification.getCooldownPeriod());
+ jgen.writeBooleanField("srActionable", notification.getSRActionable());
+ jgen.writeNumberField("severityLevel", notification.getSeverityLevel());
+
+ String customText = notification.getCustomText();
+ String notificationCustomData = notification.getNotificationCustomData();
+
+ jgen.writeArrayFieldStart("subscriptions");
+ for (String subscription : notification.getSubscriptions()) {
+ jgen.writeString(subscription.trim());
+ }
+ jgen.writeEndArray();
+
+ jgen.writeArrayFieldStart("metricsToAnnotate");
+ for (String metricToAnnotate : notification.getMetricsToAnnotate()) {
+ jgen.writeString(metricToAnnotate);
+ }
+ jgen.writeEndArray();
+
+ jgen.writeArrayFieldStart("triggers");
+ for (Trigger trigger : notification.getTriggers()) {
+ jgen.writeString(trigger.getId().toString());
+ }
+ jgen.writeEndArray();
+
+ if (customText != null) {
+ jgen.writeStringField("customText", customText);
+ }
+
+ if(notificationCustomData != null) {
+ jgen.writeStringField("notificationCustomData", notificationCustomData);
+ }
+
+ // Getting these values requires a lot of queries to rdbms at runtime, and so these are excluded for now
+ // as the current usecases do not need these values to be serialized
+ //jgen.writeObjectField("cooldownExpirationByTriggerAndMetric", notification.getCooldownExpirationMap());
+ //jgen.writeObjectField("activeStatusByTriggerAndMetric", notification.getActiveStatusMap());
+
+ jgen.writeEndObject();
+
+ }
+
+ }
+
+ public static class Deserializer extends JsonDeserializer {
+
+ @Override
+ public Notification deserialize(com.fasterxml.jackson.core.JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
+
+ Notification notification = new Notification();
+
+ JsonNode rootNode = jp.getCodec().readTree(jp);
+
+ BigInteger id = new BigInteger(rootNode.get("id").asText());
+ notification.id = id;
+
+ if (rootNode.get("modifiedDate") != null) {
+ notification.setModifiedDate(Date.from(Instant.ofEpochMilli(rootNode.get("modifiedDate").asLong())));
+ }
+
+ if (rootNode.get("createdDate") != null) {
+ notification.createdDate = Date.from(Instant.ofEpochMilli(rootNode.get("createdDate").asLong()));
+ }
+
+ String name = rootNode.get("name").asText();
+ notification.setName(name);
+
+ String notifierName = rootNode.get("notifier").asText();
+ notification.setNotifierName(notifierName);
+
+ long cooldownPeriod = rootNode.get("cooldownPeriod").asLong();
+ notification.setCooldownPeriod(cooldownPeriod);
+
+ boolean srActionable = rootNode.get("srActionable").asBoolean();
+ notification.setSRActionable(srActionable);
+
+
+ int severity = rootNode.get("severityLevel").asInt();
+ notification.setSeverityLevel(severity);
+
+ if (rootNode.get("customText") != null) {
+ notification.setCustomText(rootNode.get("customText").asText());
+ }
+
+ if (rootNode.get("notificationCustomData") != null) {
+ notification.setNotificationCustomData(rootNode.get("notificationCustomData").asText());
+ }
+
+ List subscriptions = new ArrayList<>();
+ JsonNode subscriptionsArrayNode = rootNode.get("subscriptions");
+ if (subscriptionsArrayNode.isArray()) {
+ for (JsonNode subscriptionNode : subscriptionsArrayNode) {
+ subscriptions.add(subscriptionNode.asText().trim());
+ }
+ }
+ notification.setSubscriptions(subscriptions);
+
+ List metricsToAnnotate = new ArrayList<>();
+ JsonNode metricsToAnnotateArrayNode = rootNode.get("metricsToAnnotate");
+ if (metricsToAnnotateArrayNode.isArray()) {
+ for (JsonNode metricToAnnotateNode : metricsToAnnotateArrayNode) {
+ metricsToAnnotate.add(metricToAnnotateNode.asText());
+ }
+ }
+ notification.setMetricsToAnnotate(metricsToAnnotate);
+
+ List triggers = new ArrayList<>();
+ JsonNode triggersArrayNode = rootNode.get("triggers");
+ if (triggersArrayNode.isArray()) {
+ for (JsonNode triggerNode : triggersArrayNode) {
+ BigInteger triggerId = new BigInteger(triggerNode.asText());
+ Trigger trigger = new Trigger();
+ trigger.id = triggerId;
+ triggers.add(trigger);
+ }
+ }
+ notification.setTriggers(triggers);
+
+ // Commenting this part out as these fields are not currently serialized
+ /*Map activeStatusByTriggerAndMetric = new HashMap<>();
+ JsonNode activeStatusByTriggerAndMetricNode = rootNode.get("activeStatusByTriggerAndMetric");
+ if(activeStatusByTriggerAndMetricNode.isObject()) {
+ Iterator> fieldsIter = activeStatusByTriggerAndMetricNode.fields();
+ while(fieldsIter.hasNext()) {
+ Entry field = fieldsIter.next();
+ activeStatusByTriggerAndMetric.put(field.getKey(), field.getValue().asBoolean());
+ }
+ }
+ notification.activeStatusByTriggerAndMetric = activeStatusByTriggerAndMetric;
+
+ Map cooldownExpirationByTriggerAndMetric = new HashMap<>();
+ JsonNode cooldownExpirationByTriggerAndMetricNode = rootNode.get("cooldownExpirationByTriggerAndMetric");
+ if(cooldownExpirationByTriggerAndMetricNode.isObject()) {
+ Iterator> fieldsIter = cooldownExpirationByTriggerAndMetricNode.fields();
+ while(fieldsIter.hasNext()) {
+ Entry field = fieldsIter.next();
+ cooldownExpirationByTriggerAndMetric.put(field.getKey(), field.getValue().asLong());
+ }
+ }
+ notification.cooldownExpirationByTriggerAndMetric = cooldownExpirationByTriggerAndMetric;*/
+
+ return notification;
+ }
+
+ }
//~ Instance fields ******************************************************************************************************************************
@@ -246,47 +249,60 @@ public Notification deserialize(JsonParser jp, DeserializationContext ctxt) thro
String notifierName;
@ElementCollection
- @Column(length = 2048)
+ @Column(length = 2048)
List subscriptions = new ArrayList<>(0);
@ElementCollection
- List metricsToAnnotate = new ArrayList<>(0);
+ List metricsToAnnotate = new ArrayList<>(0);
long cooldownPeriod;
- @ManyToOne(optional = false, fetch=FetchType.LAZY)
+ @ManyToOne(optional = false, fetch = FetchType.LAZY)
@JoinColumn(name = "alert_id")
private Alert alert;
- @ManyToMany
+ @ManyToMany
@JoinTable(
- name = "NOTIFICATION_TRIGGER", joinColumns = @JoinColumn(name = "TRIGGER_ID"), inverseJoinColumns = @JoinColumn(name = "NOTIFICATION_ID")
+ name = "NOTIFICATION_TRIGGER", joinColumns = @JoinColumn(name = "TRIGGER_ID"), inverseJoinColumns = @JoinColumn(name = "NOTIFICATION_ID")
)
List triggers = new ArrayList<>(0);
- boolean isSRActionable = false;
+ boolean isSRActionable = false;
- int severityLevel = 5;
+ int severityLevel = 5;
@Lob
private String customText;
+ @Lob
+ private String notificationCustomData;
+
+ private static String EVENT_NAME_KEY = "__eventName__";
+ private static String ELEMENT_NAME_KEY = "__elementName__";
+ private static String PRODUCT_TAG_KEY = "__productTag__";
+ private static String ARTICLE_NUMBER_KEY = "__articleNumber__";
+ private static String ENABLE_CLEAR_NOTIFICATION_KEY = "__enableClearNotification__"; // used by EmailNotifier and GusNotifier
+ private static String EMAIL_SUBJECT_KEY = "__emailSubject__"; // used by EmailNotifier
+
+
@ElementCollection
private Map cooldownExpirationByTriggerAndMetric = new HashMap<>();
@ElementCollection
private Map activeStatusByTriggerAndMetric = new HashMap<>();
+ @Transient
+ private final Logger _logger = LoggerFactory.getLogger(Notification.class);
//~ Constructors *********************************************************************************************************************************
/**
* Creates a new Notification object with a cool down of one hour and having specified no metrics on which to create annotations.
*
- * @param name The notification name. Cannot be null or empty.
- * @param alert The alert with which the notification is associated.
- * @param notifierName The notifier implementation class name.
- * @param subscriptions The notifier specific list of subscriptions to which notification shall be sent.
- * @param cooldownPeriod The cool down period of the notification
+ * @param name The notification name. Cannot be null or empty.
+ * @param alert The alert with which the notification is associated.
+ * @param notifierName The notifier implementation class name.
+ * @param subscriptions The notifier specific list of subscriptions to which notification shall be sent.
+ * @param cooldownPeriod The cool down period of the notification
*/
public Notification(String name, Alert alert, String notifierName, List subscriptions, long cooldownPeriod) {
super(alert.getOwner());
@@ -295,9 +311,12 @@ public Notification(String name, Alert alert, String notifierName, List
setNotifierName(notifierName);
setSubscriptions(subscriptions);
setCooldownPeriod(cooldownPeriod);
+ initializeNotificationData();
}
- /** Creates a new Notification object. */
+ /**
+ * Creates a new Notification object.
+ */
protected Notification() {
super(null);
}
@@ -305,53 +324,53 @@ protected Notification() {
//~ Static Methods *******************************************************************************************************************************
@SuppressWarnings("unchecked")
- public static void updateActiveStatusAndCooldown(EntityManager em, List notifications) {
- requireArgument(em != null, "Entity manager can not be null.");
-
- if(notifications.isEmpty()) return;
+ public static void updateActiveStatusAndCooldown(EntityManager em, List notifications) {
+ requireArgument(em != null, "Entity manager can not be null.");
- Map notificationsByIds = new HashMap<>(notifications.size());
+ if (notifications.isEmpty()) return;
- StringBuilder sb = new StringBuilder();
- for(Notification n : notifications) {
- notificationsByIds.put(n.getId(), n);
- n.activeStatusByTriggerAndMetric.clear();
- n.cooldownExpirationByTriggerAndMetric.clear();
- sb.append(n.getId()).append(",");
- }
+ Map notificationsByIds = new HashMap<>(notifications.size());
- String ids = sb.substring(0, sb.length()-1);
- try {
- Query q = em.createNativeQuery("select * from notification_cooldownexpirationbytriggerandmetric where notification_id IN (" + ids + ")");
- List