256 lines
No EOL
9.9 KiB
Java
256 lines
No EOL
9.9 KiB
Java
package com.alexanderlogistics.metrics;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.Set;
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
import java.util.logging.Logger;
|
|
|
|
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
|
import org.eclipse.microprofile.metrics.Metadata;
|
|
import org.eclipse.microprofile.metrics.MetricRegistry;
|
|
import org.eclipse.microprofile.metrics.Tag;
|
|
import org.eclipse.microprofile.metrics.annotation.RegistryScope;
|
|
import org.imixs.workflow.ItemCollection;
|
|
import org.imixs.workflow.engine.DocumentService;
|
|
import org.imixs.workflow.engine.ProcessingEvent;
|
|
import org.imixs.workflow.exceptions.PluginException;
|
|
import org.imixs.workflow.exceptions.QueryException;
|
|
|
|
import com.alexanderlogistics.InvoiceUtil;
|
|
|
|
import jakarta.annotation.security.DeclareRoles;
|
|
import jakarta.annotation.security.RolesAllowed;
|
|
import jakarta.annotation.security.RunAs;
|
|
import jakarta.ejb.TransactionAttribute;
|
|
import jakarta.ejb.TransactionAttributeType;
|
|
import jakarta.enterprise.context.ApplicationScoped;
|
|
import jakarta.enterprise.event.Observes;
|
|
import jakarta.inject.Inject;
|
|
|
|
/**
|
|
* Dieser Service reagiert auch ProcessingEvents und speichert/aktualisiert die
|
|
* zugehörige Debitoren Metric Entity.
|
|
* <p>
|
|
* Der Service liest in einem AFTER_PROCESS Event den alten invoice.saldo aus.
|
|
* Hierzu wird die Rechnung in einer neuen Transaktion geladen was einem
|
|
* 'Dirty-Read' entspricht. Dadurch kennt die Methode den letzten saldo der
|
|
* Rechnung. Hat sich nun der aktuelle Saldo geändert, aktualisiert der Serivce
|
|
* die entsprechende Metric.
|
|
*
|
|
*
|
|
*/
|
|
@DeclareRoles({ "org.imixs.ACCESSLEVEL.NOACCESS", "org.imixs.ACCESSLEVEL.READERACCESS",
|
|
"org.imixs.ACCESSLEVEL.AUTHORACCESS", "org.imixs.ACCESSLEVEL.EDITORACCESS",
|
|
"org.imixs.ACCESSLEVEL.MANAGERACCESS" })
|
|
@RolesAllowed({ "org.imixs.ACCESSLEVEL.NOACCESS", "org.imixs.ACCESSLEVEL.READERACCESS",
|
|
"org.imixs.ACCESSLEVEL.AUTHORACCESS", "org.imixs.ACCESSLEVEL.EDITORACCESS",
|
|
"org.imixs.ACCESSLEVEL.MANAGERACCESS" })
|
|
@RunAs("org.imixs.ACCESSLEVEL.MANAGERACCESS")
|
|
@ApplicationScoped
|
|
public class MetricDebitorService {
|
|
|
|
private static Logger logger = Logger.getLogger(MetricDebitorService.class.getName());
|
|
private final ConcurrentHashMap<String, ItemCollection> metricCache = new ConcurrentHashMap<>();
|
|
private final Set<String> registeredGauges = ConcurrentHashMap.newKeySet();
|
|
|
|
public static final String TYPE_METRIC_DEBITOR = "metric.debitor";
|
|
public static final String ITEM_SALDO = "invoice.saldo";
|
|
|
|
@Inject
|
|
@RegistryScope(scope = MetricRegistry.APPLICATION_SCOPE)
|
|
MetricRegistry metricRegistry;
|
|
|
|
@Inject
|
|
DocumentService documentService;
|
|
|
|
@Inject
|
|
@ConfigProperty(name = "metrics.enabled", defaultValue = "false")
|
|
private boolean metricsEnabled;
|
|
|
|
/**
|
|
* Process Metric only if some data has changed....
|
|
*
|
|
* @param processingEvent
|
|
* @throws PluginException
|
|
*/
|
|
public void onProcessingEvent(@Observes ProcessingEvent processingEvent) throws PluginException {
|
|
|
|
if (!metricsEnabled) {
|
|
return;
|
|
}
|
|
double debitorSaldo = 0;
|
|
ItemCollection invoice = processingEvent.getDocument();
|
|
if (!invoice.getModelVersion().startsWith("rechnungsausgang-")) {
|
|
// skip event
|
|
return;
|
|
}
|
|
|
|
// verify if saldo has changed.....
|
|
double invoiceSaldo = invoice.getItemValueDouble(ITEM_SALDO);
|
|
if (!"workitem".equals(invoice.getType())) {
|
|
// vorgang ist archiviert oder gelöscht worden => saldo = 0!
|
|
invoiceSaldo = 0.0;
|
|
}
|
|
String dbtrNumber = invoice.getItemValueString("dbtr.number");
|
|
String dbtrName = invoice.getItemValueString("dbtr.name");
|
|
String currency = invoice.getItemValueString("invoice.currency");
|
|
// load last metric...
|
|
double lastInvoiceSaldo = readDirtySaldo(invoice.getUniqueID());
|
|
if (invoiceSaldo == lastInvoiceSaldo) {
|
|
// no change - no metric update!
|
|
return;
|
|
}
|
|
|
|
// update metric and the metric cache
|
|
if (processingEvent.getEventType() == ProcessingEvent.AFTER_PROCESS) {
|
|
|
|
ItemCollection metricData = metricCache.get(dbtrNumber);
|
|
if (metricData == null) {
|
|
metricData = loadMetric(dbtrNumber, dbtrName);
|
|
}
|
|
// Saldo-Berechnung
|
|
debitorSaldo = metricData.getItemValueDouble(ITEM_SALDO + "." + currency); // totals.get(currency);
|
|
debitorSaldo = debitorSaldo - lastInvoiceSaldo;
|
|
|
|
// update debitor saldo
|
|
debitorSaldo = InvoiceUtil.round(debitorSaldo + invoiceSaldo);
|
|
|
|
metricData.setItemValue(ITEM_SALDO + "." + currency, debitorSaldo);
|
|
metricCache.put(dbtrNumber, metricData);
|
|
// Speichern in der Datenbank
|
|
logger.info("Update metric for debitor: " + dbtrNumber + " -> " + debitorSaldo);
|
|
documentService.save(metricData);
|
|
|
|
// Gauge registrieren
|
|
updateGauge(metricData, currency);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This helper method reads the 'dirty' saldo in a new transaction. This is used
|
|
* for calculating the new total saldo
|
|
*
|
|
* @param uniqueid
|
|
*/
|
|
@TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW)
|
|
public double readDirtySaldo(String uniqueid) {
|
|
double result = 0;
|
|
ItemCollection dirtyInvoice = documentService.load(uniqueid);
|
|
if (dirtyInvoice == null) {
|
|
return 0.0;
|
|
}
|
|
result = dirtyInvoice.getItemValueDouble(ITEM_SALDO);
|
|
// logger.info("---Dirty last Saldo was : " + result);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* This method loads a metric entity. If no metric entity exits, the method
|
|
* creates a new one.
|
|
*
|
|
* @param dbtrID
|
|
* @return
|
|
* @throws PluginException
|
|
*/
|
|
public ItemCollection loadMetric(String dbtrNumber, String dbtrName) throws PluginException {
|
|
|
|
ItemCollection debitorMetric = null;
|
|
if (dbtrNumber == null || dbtrNumber.isEmpty()) {
|
|
throw new PluginException(PluginException.class.getName(), "QUERY ERROR",
|
|
"missing debitor number");
|
|
}
|
|
try {
|
|
String query = "(type:" + TYPE_METRIC_DEBITOR + ") AND (name:" + dbtrNumber + ")";
|
|
List<ItemCollection> result = documentService.find(query, 1, 0, "$modified", true);
|
|
if (result.size() > 0) {
|
|
debitorMetric = result.get(0);
|
|
}
|
|
if (debitorMetric == null) {
|
|
// create a new instance
|
|
// logger.info("creating new debitor metric");
|
|
debitorMetric = createMetaData(dbtrNumber, dbtrName);
|
|
|
|
}
|
|
} catch (QueryException e) {
|
|
throw new PluginException(PluginException.class.getName(), "QUERY ERROR", e.getMessage(), e);
|
|
}
|
|
|
|
return debitorMetric;
|
|
}
|
|
|
|
/**
|
|
* Creates an empty Debitor Meta Data Object (ItemCollection)
|
|
* <p>
|
|
* The ItemCollection stores the name and number and also all saldos for all
|
|
* currencies
|
|
*
|
|
* @param dbtrNumber
|
|
* @param dbtrName
|
|
* @return
|
|
*/
|
|
private ItemCollection createMetaData(String dbtrNumber, String dbtrName) {
|
|
ItemCollection metricData = new ItemCollection();
|
|
|
|
metricData = new ItemCollection();
|
|
metricData.setType(TYPE_METRIC_DEBITOR);
|
|
metricData.setItemValue("name", dbtrNumber);
|
|
metricData.setItemValue("dbtr.number", dbtrNumber);
|
|
metricData.setItemValue("dbtr.name", dbtrName);
|
|
return metricData;
|
|
}
|
|
|
|
/**
|
|
* Helper method to register a gauge for a debitor
|
|
*
|
|
* @param dbtrNumber - the debitor number
|
|
* @param dbtrName - the debitor name
|
|
*/
|
|
private void updateGauge(ItemCollection metricData, String currency) {
|
|
String dbtrNumber = metricData.getItemValueString("dbtr.number");
|
|
String dbtrName = metricData.getItemValueString("dbtr.name");
|
|
String metricKey = "dbtr_" + dbtrNumber + "_" + dbtrName + "_" + currency;
|
|
// Prüfen ob Gauge bereits registriert ist
|
|
if (registeredGauges.add(metricKey)) { // returns true newly added
|
|
List<Tag> tags = new ArrayList<>();
|
|
tags.add(new Tag("type", "dbtr"));
|
|
tags.add(new Tag("number", dbtrNumber));
|
|
tags.add(new Tag("name", dbtrName));
|
|
tags.add(new Tag("currency", currency));
|
|
logger.info("register new metric for debitor: " + dbtrNumber + " -> " + currency);
|
|
Metadata metadata = Metadata.builder()
|
|
.withName("dbtr.invoice.saldo")
|
|
.withDescription("Debitor Balance by Currency")
|
|
.build();
|
|
metricRegistry.gauge(metadata,
|
|
() -> metricCache.getOrDefault(dbtrNumber, createMetaData(dbtrNumber, dbtrName))
|
|
.getItemValueDouble(ITEM_SALDO + "." + currency),
|
|
tags.toArray(new Tag[0]));
|
|
} else {
|
|
logger.fine("Gauge already registered for debitor: " + dbtrNumber + " -> " + currency);
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Hilfsmethode um Metriken initial aufzubauen. Wird vom Metric Rest Service
|
|
* verwendet.
|
|
*
|
|
*/
|
|
protected void initMetric(ItemCollection metric) {
|
|
String dbtrNumber = metric.getItemValueString("dbtr.number");
|
|
// Aktuellen Saldo in Map speichern
|
|
metricCache.put(dbtrNumber, metric);
|
|
// Gauge für jede Währung registrieren
|
|
for (String itemName : metric.getItemNames()) {
|
|
if (itemName.startsWith(ITEM_SALDO + ".")) {
|
|
// currency value found
|
|
int pos = (ITEM_SALDO + ".").length();
|
|
String currency = itemName.substring(pos).toUpperCase();
|
|
logger.info("│ │ ├──init metric for debitor " + dbtrNumber + " -> " + currency);
|
|
updateGauge(metric, currency);
|
|
}
|
|
}
|
|
}
|
|
|
|
} |