impl metric service
This commit is contained in:
parent
2fe0f4ef69
commit
07ed3a30d0
5 changed files with 613 additions and 64 deletions
|
|
@ -0,0 +1,145 @@
|
||||||
|
package com.alexanderlogistics.metrics;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import org.imixs.workflow.ItemCollection;
|
||||||
|
import org.imixs.workflow.engine.DocumentService;
|
||||||
|
import org.imixs.workflow.exceptions.PluginException;
|
||||||
|
import org.imixs.workflow.exceptions.QueryException;
|
||||||
|
|
||||||
|
import jakarta.ejb.Stateless;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
import jakarta.ws.rs.GET;
|
||||||
|
import jakarta.ws.rs.Path;
|
||||||
|
import jakarta.ws.rs.Produces;
|
||||||
|
import jakarta.ws.rs.core.MediaType;
|
||||||
|
import jakarta.ws.rs.core.Response;
|
||||||
|
|
||||||
|
@Path("metrics/cdtr")
|
||||||
|
@Stateless
|
||||||
|
public class MetricCreditorRestService {
|
||||||
|
|
||||||
|
private static Logger logger = Logger.getLogger(MetricCreditorRestService.class.getName());
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
DocumentService documentService;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
MetricCreditorService metricService;
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/ping")
|
||||||
|
@Produces({ MediaType.TEXT_PLAIN })
|
||||||
|
public String ping() {
|
||||||
|
logger.info("GET ping");
|
||||||
|
return "ping: " + System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method refreshes all creditor metrics by iterating through the metric
|
||||||
|
* entities.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@GET
|
||||||
|
@Path("/refresh")
|
||||||
|
@Produces({ MediaType.TEXT_PLAIN })
|
||||||
|
public Response refreshMetrics() {
|
||||||
|
try {
|
||||||
|
// Alle Creditor Metriken laden
|
||||||
|
String query = "(type:" + MetricCreditorService.TYPE_METRIC_CREDITOR + ")";
|
||||||
|
List<ItemCollection> metrics = documentService.find(query, 9999, 0);
|
||||||
|
|
||||||
|
// Metriken initialisieren
|
||||||
|
for (ItemCollection metric : metrics) {
|
||||||
|
metricService.initMetric(metric);
|
||||||
|
}
|
||||||
|
return Response.ok().entity("Initialized " + metrics.size() + " creditor metrics").build();
|
||||||
|
} catch (Exception e) {
|
||||||
|
return Response.serverError()
|
||||||
|
.entity("Failed to initialize metrics: " + e.getMessage())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method initializes the metrics for all creditors with open invoices.
|
||||||
|
* The method creates or updates the metric entires for each creditor.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@GET
|
||||||
|
@Path("/init")
|
||||||
|
@Produces({ MediaType.TEXT_PLAIN })
|
||||||
|
public Response initMetrics() {
|
||||||
|
// Map<String, ItemCollection> metricCache = new HashMap<String,
|
||||||
|
// ItemCollection>();
|
||||||
|
long l = System.currentTimeMillis();
|
||||||
|
logger.info("├── init cdtr metrics...");
|
||||||
|
try {
|
||||||
|
groupInvoicesByCreditor();
|
||||||
|
logger.info("│ ├── grouping invoices finished in " + (System.currentTimeMillis() - l) + "ms");
|
||||||
|
|
||||||
|
rebuildMetrics();
|
||||||
|
String message = "├── init cdtr metrics completed in "
|
||||||
|
+ (System.currentTimeMillis() - l)
|
||||||
|
+ "ms";
|
||||||
|
logger.info(message);
|
||||||
|
return Response.ok().entity(message).build();
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return Response.serverError()
|
||||||
|
.entity("Failed to initialize metrics: " + e.getMessage())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void rebuildMetrics() throws PluginException {
|
||||||
|
logger.info("│ ├── rebuild metrics...");
|
||||||
|
List<String> keys = metricService.getMetricKeys();
|
||||||
|
for (String hashKey : keys) {
|
||||||
|
ItemCollection metricData = metricService.getMetric(hashKey);
|
||||||
|
// metricCache.get(dbtrNumber);
|
||||||
|
documentService.save(metricData);
|
||||||
|
metricService.initMetric(metricData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Diese Methode gruppiert eine Rechnungsliste nach creditoren
|
||||||
|
*
|
||||||
|
* @param spaceID - Space Ref to select a list of invoices associated with
|
||||||
|
* a
|
||||||
|
* space
|
||||||
|
* @param metricCache - a local cache storing all invoices by week
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private void groupInvoicesByCreditor() {
|
||||||
|
logger.info("│ │ ├── group invoices by creditor...");
|
||||||
|
try {
|
||||||
|
List<ItemCollection> invoices = documentService.find(
|
||||||
|
"($modelversion:rechnungseingang-* OR $modelversion:gutschriftabgleich-*) " +
|
||||||
|
" AND type:workitem",
|
||||||
|
9999, 0,
|
||||||
|
"invoice.number", false);
|
||||||
|
logger.info("│ │ ├──found " + invoices.size() + " open invoices");
|
||||||
|
for (ItemCollection invoice : invoices) {
|
||||||
|
if (invoice.getItemValueString("cdtr.number").trim().isEmpty()) {
|
||||||
|
// skip event
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ItemCollection metricData = metricService.getMetricByInvoice(invoice);
|
||||||
|
// Jetzt Rechnung addieren
|
||||||
|
metricService.addInvoice(metricData, invoice);
|
||||||
|
logger.fine("....put invoice " + invoice.getUniqueID());
|
||||||
|
metricService.putMetric(metricData);
|
||||||
|
}
|
||||||
|
logger.info("│ │ ├── grouped " + invoices.size() + " invoices.");
|
||||||
|
} catch (QueryException | PluginException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,356 @@
|
||||||
|
package com.alexanderlogistics.metrics;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
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.enterprise.context.ApplicationScoped;
|
||||||
|
import jakarta.enterprise.event.Observes;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dieser Service reagiert auch ProcessingEvents und speichert/aktualisiert die
|
||||||
|
* zugehörige Creditor Metric Entity (type=metric.debitor).
|
||||||
|
* <p>
|
||||||
|
* Beim Rechnungseingang gilt eine Rechnung als Bezahlt wenn diese einen finalen
|
||||||
|
* Status erreicht hat (>=5800)
|
||||||
|
* <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 MetricCreditorService {
|
||||||
|
|
||||||
|
private static Logger logger = Logger.getLogger(MetricCreditorService.class.getName());
|
||||||
|
private final ConcurrentHashMap<String, ItemCollection> metricCache = new ConcurrentHashMap<>();
|
||||||
|
private final Set<String> registeredGauges = ConcurrentHashMap.newKeySet();
|
||||||
|
|
||||||
|
public static final String TYPE_METRIC_CREDITOR = "metric.creditor";
|
||||||
|
public static final String ITEM_TOTAL = "invoice.total";
|
||||||
|
public static final String ITEM_SALDO = "invoice.saldo";
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
@RegistryScope(scope = MetricRegistry.APPLICATION_SCOPE)
|
||||||
|
MetricRegistry metricRegistry;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
DocumentService documentService;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
MetricDataService metricDataService;
|
||||||
|
|
||||||
|
@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) {
|
||||||
|
long l = System.currentTimeMillis();
|
||||||
|
if (!metricsEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// double creditorSaldo = 0;
|
||||||
|
ItemCollection invoice = processingEvent.getDocument();
|
||||||
|
if (!invoice.getModelVersion().startsWith("rechnungseingang-")
|
||||||
|
&& !invoice.getModelVersion().startsWith("gutschriftabgleich-")) {
|
||||||
|
// skip event
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we do not have a cdtr.number skip
|
||||||
|
if (invoice.getItemValueString("cdtr.number").trim().isEmpty()) {
|
||||||
|
// skip event
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// update metric and the metric cache
|
||||||
|
if (processingEvent.getEventType() == ProcessingEvent.AFTER_PROCESS) {
|
||||||
|
|
||||||
|
// load the last invoice metric and reduce the saldo...
|
||||||
|
ItemCollection lastInvoice = metricDataService.readDirtyWorkitem(invoice.getUniqueID());
|
||||||
|
ItemCollection lastMetricData = getMetricByInvoice(lastInvoice);
|
||||||
|
subtractInvoice(lastMetricData, lastInvoice);
|
||||||
|
putMetric(lastMetricData);
|
||||||
|
metricDataService.saveMetric(lastMetricData);
|
||||||
|
|
||||||
|
// load the invoice metric and add the saldo...
|
||||||
|
ItemCollection metricData = getMetricByInvoice(invoice);
|
||||||
|
// Saldo-Berechnung
|
||||||
|
addInvoice(metricData, invoice);
|
||||||
|
putMetric(metricData);
|
||||||
|
metricDataService.saveMetric(metricData);
|
||||||
|
|
||||||
|
// Update the Gauge
|
||||||
|
updateGauge(metricData);
|
||||||
|
logger.info("Metric cdtr update took " + (System.currentTimeMillis() - l) + "ms");
|
||||||
|
}
|
||||||
|
} catch (PluginException e) {
|
||||||
|
logger.warning("unable to process metric: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the corresponding metric data object for an invoice. The method uses
|
||||||
|
* an internal cache. If the metric was not yet cached the method loads the
|
||||||
|
* metric from the database. If not metric exists in the database the method
|
||||||
|
* automatically creates a new metric data object.
|
||||||
|
*
|
||||||
|
* @param invoice
|
||||||
|
* @return
|
||||||
|
* @throws PluginException
|
||||||
|
*/
|
||||||
|
public ItemCollection getMetricByInvoice(ItemCollection invoice) throws PluginException {
|
||||||
|
String metricKey = buildKeyByInvoice(invoice);
|
||||||
|
ItemCollection metricData = metricCache.get(metricKey);
|
||||||
|
if (metricData == null) {
|
||||||
|
metricData = loadMetric(invoice);
|
||||||
|
}
|
||||||
|
// if metric still null we create a new metric data object.
|
||||||
|
if (metricData == null) {
|
||||||
|
metricData = createMetaData(invoice);
|
||||||
|
}
|
||||||
|
return metricData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a metric data object by key
|
||||||
|
*
|
||||||
|
* @param key
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public ItemCollection getMetric(String key) {
|
||||||
|
return metricCache.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Puts a metric data object into the cache.
|
||||||
|
*
|
||||||
|
* @param metricData
|
||||||
|
*/
|
||||||
|
public void putMetric(ItemCollection metricData) {
|
||||||
|
metricCache.put(metricData.getItemValueString("name"), metricData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list with all cached metric keys.
|
||||||
|
* The method returns an unmodifiable list to prevent modifications.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public List<String> getMetricKeys() {
|
||||||
|
return Collections.unmodifiableList(new ArrayList<>(metricCache.keySet()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the metric hash key by the invoice attributes. The returned key can be
|
||||||
|
* used for caching the metric.
|
||||||
|
*
|
||||||
|
* @param invoice - The invoice ItemCollection containing the necessary
|
||||||
|
* attributes
|
||||||
|
* @return A unique hash string based on the invoice attributes
|
||||||
|
* @throws IllegalArgumentException if the invoice is null
|
||||||
|
*/
|
||||||
|
private static String buildKeyByInvoice(ItemCollection invoice) {
|
||||||
|
// Validate input
|
||||||
|
Objects.requireNonNull(invoice, "Invoice must not be null");
|
||||||
|
|
||||||
|
String cdtrNumber = invoice.getItemValueString("cdtr.number");
|
||||||
|
if (cdtrNumber.startsWith("D") || cdtrNumber.startsWith("K")) {
|
||||||
|
cdtrNumber = cdtrNumber.substring(1);
|
||||||
|
}
|
||||||
|
String cdtrName = invoice.getItemValueString("cdtr.name");
|
||||||
|
String currency = invoice.getItemValueString("invoice.currency");
|
||||||
|
String department = invoice.getItemValueString("space.name");
|
||||||
|
|
||||||
|
// Concatenate the values and create a hash
|
||||||
|
String combinedValue = String.format("%s::%s::%s::%s",
|
||||||
|
cdtrNumber,
|
||||||
|
cdtrName,
|
||||||
|
currency,
|
||||||
|
department);
|
||||||
|
|
||||||
|
String hash = String.valueOf(combinedValue.hashCode());
|
||||||
|
// a hash can start with '-' which we need to avoid and create a alphanumeric
|
||||||
|
// key instead!
|
||||||
|
return "HASH" + hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method loads a metric entity for a given invoice workitem. If no metric
|
||||||
|
* entity exits, the method
|
||||||
|
* creates a new metric entity.
|
||||||
|
*
|
||||||
|
* @param invoice
|
||||||
|
* @return
|
||||||
|
* @throws PluginException
|
||||||
|
*/
|
||||||
|
private ItemCollection loadMetric(ItemCollection invoice) throws PluginException {
|
||||||
|
ItemCollection debitorMetric = null;
|
||||||
|
try {
|
||||||
|
String metricKey = buildKeyByInvoice(invoice);
|
||||||
|
String query = "(type:" + TYPE_METRIC_CREDITOR + ") AND (name:" + metricKey + ")";
|
||||||
|
List<ItemCollection> result = documentService.find(query, 1, 0, "$modified", true);
|
||||||
|
if (result.size() > 0) {
|
||||||
|
debitorMetric = result.get(0);
|
||||||
|
}
|
||||||
|
} catch (IllegalArgumentException | QueryException e) {
|
||||||
|
throw new PluginException(PluginException.class.getName(),
|
||||||
|
"Failed to load metric object for invoice " + invoice.getUniqueID() + ": ", 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 invoice - invoice ItemCollection
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private ItemCollection createMetaData(ItemCollection invoice) {
|
||||||
|
String key = buildKeyByInvoice(invoice);
|
||||||
|
ItemCollection metricData = new ItemCollection();
|
||||||
|
metricData.setType(TYPE_METRIC_CREDITOR);
|
||||||
|
metricData.setItemValue("name", key);
|
||||||
|
|
||||||
|
String cdtrNumber = invoice.getItemValueString("cdtr.number");
|
||||||
|
if (cdtrNumber.startsWith("D") || cdtrNumber.startsWith("K")) {
|
||||||
|
cdtrNumber = cdtrNumber.substring(1);
|
||||||
|
}
|
||||||
|
metricData.setItemValue("cdtr.number", cdtrNumber);
|
||||||
|
metricData.setItemValue("cdtr.name", invoice.getItemValueString("cdtr.name"));
|
||||||
|
metricData.setItemValue("invoice.currency", invoice.getItemValueString("invoice.currency"));
|
||||||
|
metricData.setItemValue("space.name", invoice.getItemValueString("space.name"));
|
||||||
|
return metricData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to register a gauge for a debitor
|
||||||
|
*
|
||||||
|
* @param cdtrNumber - the creditor number
|
||||||
|
* @param cdtrName - the creditor name
|
||||||
|
*/
|
||||||
|
private void updateGauge(ItemCollection metricData) {
|
||||||
|
String cdtrNumber = metricData.getItemValueString("cdtr.number");
|
||||||
|
String cdtrName = metricData.getItemValueString("cdtr.name");
|
||||||
|
String metricKey = metricData.getItemValueString("name");
|
||||||
|
// 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", "cdtr"));
|
||||||
|
tags.add(new Tag("number", cdtrNumber));
|
||||||
|
tags.add(new Tag("name", cdtrName));
|
||||||
|
tags.add(new Tag("currency", metricData.getItemValueString("invoice.currency")));
|
||||||
|
tags.add(new Tag("department", metricData.getItemValueString("space.name")));
|
||||||
|
logger.info("register new metric for creditor: " + cdtrNumber +
|
||||||
|
", " + metricData.getItemValueString(ITEM_SALDO) +
|
||||||
|
" " + metricData.getItemValueString("invoice.currency"));
|
||||||
|
Metadata metadata = Metadata.builder()
|
||||||
|
.withName("cdtr.balance")
|
||||||
|
.withDescription("Creditor Balance")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
metricCache.get(metricKey);
|
||||||
|
metricRegistry.gauge(metadata,
|
||||||
|
() -> metricCache.get(metricKey).getItemValueDouble(ITEM_SALDO),
|
||||||
|
tags.toArray(new Tag[0]));
|
||||||
|
} else {
|
||||||
|
logger.fine("Gauge already registered for creditor: " + cdtrNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hilfsmethode um Metriken initial aufzubauen. Wird vom Metric Rest Service
|
||||||
|
* verwendet.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected void initMetric(ItemCollection metric) {
|
||||||
|
String key = metric.getItemValueString("name");
|
||||||
|
// Aktuelle metric cachen
|
||||||
|
metricCache.put(key, metric);
|
||||||
|
logger.info("│ │ ├──init metric for creditor " + key);
|
||||||
|
updateGauge(metric);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Addiert den saldo einer Invoice zu einem metricData object
|
||||||
|
*
|
||||||
|
* @param metricData
|
||||||
|
* @param invoice
|
||||||
|
*/
|
||||||
|
public void addInvoice(ItemCollection metricData, ItemCollection invoice) {
|
||||||
|
double invoiceTotal = invoice.getItemValueDouble(ITEM_TOTAL);
|
||||||
|
if (!"workitem".equals(invoice.getType()) || invoice.getTaskID() >= 5800) {
|
||||||
|
// vorgang ist archiviert oder gelöscht worden => saldo = 0!
|
||||||
|
invoiceTotal = 0.0;
|
||||||
|
}
|
||||||
|
// is the metric new?
|
||||||
|
if (!metricData.hasItem(ITEM_SALDO)) {
|
||||||
|
// init metric with the total value!
|
||||||
|
metricData.setItemValue(ITEM_SALDO, InvoiceUtil.round(invoiceTotal));
|
||||||
|
} else {
|
||||||
|
double lastSaldo = metricData.getItemValueDouble(ITEM_SALDO);
|
||||||
|
metricData.setItemValue(ITEM_SALDO, InvoiceUtil.round(lastSaldo + invoiceTotal));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void subtractInvoice(ItemCollection metricData, ItemCollection invoice) {
|
||||||
|
double invoiceTotal = invoice.getItemValueDouble(ITEM_TOTAL);
|
||||||
|
if (!"workitem".equals(invoice.getType()) || invoice.getTaskID() >= 5800) {
|
||||||
|
// vorgang ist archiviert oder gelöscht worden => saldo = 0!
|
||||||
|
invoiceTotal = 0.0;
|
||||||
|
}
|
||||||
|
// is the metric new?
|
||||||
|
if (!metricData.hasItem(ITEM_SALDO)) {
|
||||||
|
// init metric with the total value!
|
||||||
|
metricData.setItemValue(ITEM_SALDO, invoiceTotal);
|
||||||
|
} else {
|
||||||
|
double lastSaldo = metricData.getItemValueDouble(ITEM_SALDO);
|
||||||
|
metricData.setItemValue(ITEM_SALDO, lastSaldo - invoiceTotal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
package com.alexanderlogistics.metrics;
|
||||||
|
|
||||||
|
import org.imixs.workflow.ItemCollection;
|
||||||
|
import org.imixs.workflow.engine.DocumentService;
|
||||||
|
|
||||||
|
import jakarta.annotation.security.DeclareRoles;
|
||||||
|
import jakarta.annotation.security.RunAs;
|
||||||
|
import jakarta.ejb.Stateless;
|
||||||
|
import jakarta.ejb.TransactionAttribute;
|
||||||
|
import jakarta.ejb.TransactionAttributeType;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The MetricDataService provides methods to lookup and update metric data
|
||||||
|
* objects.
|
||||||
|
* The service runs with manager access.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@DeclareRoles({ "org.imixs.ACCESSLEVEL.MANAGERACCESS" })
|
||||||
|
@RunAs("org.imixs.ACCESSLEVEL.MANAGERACCESS")
|
||||||
|
@Stateless
|
||||||
|
public class MetricDataService {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
DocumentService documentService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs with Manager access
|
||||||
|
*
|
||||||
|
* @param metricData
|
||||||
|
*/
|
||||||
|
public void saveMetric(ItemCollection metricData) {
|
||||||
|
documentService.save(metricData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This helper method reads a 'dirty' workitem in a new transaction. This is
|
||||||
|
* used for calculating the new metric values
|
||||||
|
*
|
||||||
|
* @param uniqueId
|
||||||
|
*/
|
||||||
|
@TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW)
|
||||||
|
public ItemCollection readDirtyWorkitem(String uniqueId) {
|
||||||
|
ItemCollection dirtyInvoice = documentService.load(uniqueId);
|
||||||
|
return dirtyInvoice;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -8,8 +8,6 @@ import org.imixs.workflow.engine.DocumentService;
|
||||||
import org.imixs.workflow.exceptions.PluginException;
|
import org.imixs.workflow.exceptions.PluginException;
|
||||||
import org.imixs.workflow.exceptions.QueryException;
|
import org.imixs.workflow.exceptions.QueryException;
|
||||||
|
|
||||||
import com.alexanderlogistics.InvoiceUtil;
|
|
||||||
|
|
||||||
import jakarta.ejb.Stateless;
|
import jakarta.ejb.Stateless;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
import jakarta.ws.rs.GET;
|
import jakarta.ws.rs.GET;
|
||||||
|
|
@ -18,7 +16,7 @@ import jakarta.ws.rs.Produces;
|
||||||
import jakarta.ws.rs.core.MediaType;
|
import jakarta.ws.rs.core.MediaType;
|
||||||
import jakarta.ws.rs.core.Response;
|
import jakarta.ws.rs.core.Response;
|
||||||
|
|
||||||
@Path("metrics")
|
@Path("metrics/dbtr")
|
||||||
@Stateless
|
@Stateless
|
||||||
public class MetricDebitorRestService {
|
public class MetricDebitorRestService {
|
||||||
|
|
||||||
|
|
@ -80,13 +78,13 @@ public class MetricDebitorRestService {
|
||||||
// Map<String, ItemCollection> metricCache = new HashMap<String,
|
// Map<String, ItemCollection> metricCache = new HashMap<String,
|
||||||
// ItemCollection>();
|
// ItemCollection>();
|
||||||
long l = System.currentTimeMillis();
|
long l = System.currentTimeMillis();
|
||||||
logger.info("├── init metrics...");
|
logger.info("├── init dbtr metrics...");
|
||||||
try {
|
try {
|
||||||
groupInvoicesByDebitor();
|
groupInvoicesByDebitor();
|
||||||
logger.info("│ ├── grouping invoices finished in " + (System.currentTimeMillis() - l) + "ms");
|
logger.info("│ ├── grouping invoices finished in " + (System.currentTimeMillis() - l) + "ms");
|
||||||
|
|
||||||
rebuildMetrics();
|
rebuildMetrics();
|
||||||
String message = "├── init metrics completed in "
|
String message = "├── init cdtr metrics completed in "
|
||||||
+ (System.currentTimeMillis() - l)
|
+ (System.currentTimeMillis() - l)
|
||||||
+ "ms";
|
+ "ms";
|
||||||
logger.info(message);
|
logger.info(message);
|
||||||
|
|
@ -135,11 +133,9 @@ public class MetricDebitorRestService {
|
||||||
for (ItemCollection invoice : invoices) {
|
for (ItemCollection invoice : invoices) {
|
||||||
ItemCollection metricData = metricService.getMetricByInvoice(invoice);
|
ItemCollection metricData = metricService.getMetricByInvoice(invoice);
|
||||||
// Jetzt Rechnung addieren
|
// Jetzt Rechnung addieren
|
||||||
addInvoice(metricData, invoice);
|
metricService.addInvoice(metricData, invoice);
|
||||||
// invoiceData.add(invoice);
|
|
||||||
logger.fine("....put invoice " + invoice.getUniqueID());
|
logger.fine("....put invoice " + invoice.getUniqueID());
|
||||||
metricService.putMetric(metricData);
|
metricService.putMetric(metricData);
|
||||||
// metricCache.put(metricKey, metricData);
|
|
||||||
}
|
}
|
||||||
logger.info("│ │ ├── grouped " + invoices.size() + " invoices.");
|
logger.info("│ │ ├── grouped " + invoices.size() + " invoices.");
|
||||||
} catch (QueryException | PluginException e) {
|
} catch (QueryException | PluginException e) {
|
||||||
|
|
@ -148,16 +144,4 @@ public class MetricDebitorRestService {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Addiert den saldo einer Invoice zu einem metricData object
|
|
||||||
*
|
|
||||||
* @param metricData
|
|
||||||
* @param invoice
|
|
||||||
*/
|
|
||||||
public void addInvoice(ItemCollection metricData, ItemCollection invoice) {
|
|
||||||
double saldo = invoice.getItemValueDouble(MetricDebitorService.ITEM_SALDO);
|
|
||||||
double saldoOld = metricData.getItemValueDouble(MetricDebitorService.ITEM_SALDO);
|
|
||||||
double saldoNew = InvoiceUtil.round(saldoOld + saldo);
|
|
||||||
metricData.setItemValue(MetricDebitorService.ITEM_SALDO, saldoNew);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,6 @@ import com.alexanderlogistics.InvoiceUtil;
|
||||||
import jakarta.annotation.security.DeclareRoles;
|
import jakarta.annotation.security.DeclareRoles;
|
||||||
import jakarta.annotation.security.RolesAllowed;
|
import jakarta.annotation.security.RolesAllowed;
|
||||||
import jakarta.annotation.security.RunAs;
|
import jakarta.annotation.security.RunAs;
|
||||||
import jakarta.ejb.TransactionAttribute;
|
|
||||||
import jakarta.ejb.TransactionAttributeType;
|
|
||||||
import jakarta.enterprise.context.ApplicationScoped;
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
import jakarta.enterprise.event.Observes;
|
import jakarta.enterprise.event.Observes;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
|
|
@ -66,6 +64,9 @@ public class MetricDebitorService {
|
||||||
@Inject
|
@Inject
|
||||||
DocumentService documentService;
|
DocumentService documentService;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
MetricDataService metricDataService;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@ConfigProperty(name = "metrics.enabled", defaultValue = "false")
|
@ConfigProperty(name = "metrics.enabled", defaultValue = "false")
|
||||||
private boolean metricsEnabled;
|
private boolean metricsEnabled;
|
||||||
|
|
@ -77,48 +78,44 @@ public class MetricDebitorService {
|
||||||
* @throws PluginException
|
* @throws PluginException
|
||||||
*/
|
*/
|
||||||
public void onProcessingEvent(@Observes ProcessingEvent processingEvent) {
|
public void onProcessingEvent(@Observes ProcessingEvent processingEvent) {
|
||||||
|
long l = System.currentTimeMillis();
|
||||||
if (!metricsEnabled) {
|
if (!metricsEnabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
double debitorSaldo = 0;
|
|
||||||
|
// double debitorSaldo = 0;
|
||||||
ItemCollection invoice = processingEvent.getDocument();
|
ItemCollection invoice = processingEvent.getDocument();
|
||||||
if (!invoice.getModelVersion().startsWith("rechnungsausgang-")) {
|
if (!invoice.getModelVersion().startsWith("rechnungsausgang-")) {
|
||||||
// skip event
|
// skip event
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if we do not have a dbtr.number skip
|
||||||
|
if (invoice.getItemValueString("dbtr.number").trim().isEmpty()) {
|
||||||
|
// skip event
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
// update metric and the metric cache
|
// update metric and the metric cache
|
||||||
if (processingEvent.getEventType() == ProcessingEvent.AFTER_PROCESS) {
|
if (processingEvent.getEventType() == ProcessingEvent.AFTER_PROCESS) {
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// load last metric...
|
// load the last invoice metric and reduce the saldo...
|
||||||
double lastInvoiceSaldo = readDirtySaldo(invoice.getUniqueID());
|
ItemCollection lastInvoice = metricDataService.readDirtyWorkitem(invoice.getUniqueID());
|
||||||
if (invoiceSaldo == lastInvoiceSaldo) {
|
ItemCollection lastMetricData = getMetricByInvoice(lastInvoice);
|
||||||
// no change - no metric update!
|
subtractInvoice(lastMetricData, lastInvoice);
|
||||||
return;
|
putMetric(lastMetricData);
|
||||||
}
|
metricDataService.saveMetric(lastMetricData);
|
||||||
|
|
||||||
|
// load the invoice metric and add the saldo...
|
||||||
ItemCollection metricData = getMetricByInvoice(invoice);
|
ItemCollection metricData = getMetricByInvoice(invoice);
|
||||||
|
|
||||||
// Saldo-Berechnung
|
// Saldo-Berechnung
|
||||||
debitorSaldo = metricData.getItemValueDouble(ITEM_SALDO);
|
addInvoice(metricData, invoice);
|
||||||
debitorSaldo = debitorSaldo - lastInvoiceSaldo;
|
|
||||||
|
|
||||||
// update debitor saldo
|
|
||||||
debitorSaldo = InvoiceUtil.round(debitorSaldo + invoiceSaldo);
|
|
||||||
metricData.setItemValue(ITEM_SALDO, debitorSaldo);
|
|
||||||
putMetric(metricData);
|
putMetric(metricData);
|
||||||
documentService.save(metricData);
|
metricDataService.saveMetric(metricData);
|
||||||
|
|
||||||
// Gauge registrieren
|
// Update the Gauge
|
||||||
updateGauge(metricData);
|
updateGauge(metricData);
|
||||||
|
logger.info("Metric dbtr update took " + (System.currentTimeMillis() - l) + "ms");
|
||||||
}
|
}
|
||||||
} catch (PluginException e) {
|
} catch (PluginException e) {
|
||||||
logger.warning("unable to process metric: " + e.getMessage());
|
logger.warning("unable to process metric: " + e.getMessage());
|
||||||
|
|
@ -208,24 +205,6 @@ public class MetricDebitorService {
|
||||||
return "HASH" + hash;
|
return "HASH" + hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 for a given invoice workitem. If no metric
|
* This method loads a metric entity for a given invoice workitem. If no metric
|
||||||
* entity exits, the method
|
* entity exits, the method
|
||||||
|
|
@ -248,7 +227,6 @@ public class MetricDebitorService {
|
||||||
throw new PluginException(PluginException.class.getName(),
|
throw new PluginException(PluginException.class.getName(),
|
||||||
"Failed to load metric object for invoice " + invoice.getUniqueID() + ": ", e.getMessage(), e);
|
"Failed to load metric object for invoice " + invoice.getUniqueID() + ": ", e.getMessage(), e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return debitorMetric;
|
return debitorMetric;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -322,4 +300,41 @@ public class MetricDebitorService {
|
||||||
updateGauge(metric);
|
updateGauge(metric);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Addiert den saldo einer Invoice zu einem metricData object
|
||||||
|
*
|
||||||
|
* @param metricData
|
||||||
|
* @param invoice
|
||||||
|
*/
|
||||||
|
public void addInvoice(ItemCollection metricData, ItemCollection invoice) {
|
||||||
|
double invoiceTotal = invoice.getItemValueDouble(ITEM_SALDO);
|
||||||
|
if (!"workitem".equals(invoice.getType()) || invoice.getTaskID() >= 5800) {
|
||||||
|
// vorgang ist archiviert oder gelöscht worden => saldo = 0!
|
||||||
|
invoiceTotal = 0.0;
|
||||||
|
}
|
||||||
|
// is the metric new?
|
||||||
|
if (!metricData.hasItem(ITEM_SALDO)) {
|
||||||
|
// init metric with the total value!
|
||||||
|
metricData.setItemValue(ITEM_SALDO, InvoiceUtil.round(invoiceTotal));
|
||||||
|
} else {
|
||||||
|
double lastSaldo = metricData.getItemValueDouble(ITEM_SALDO);
|
||||||
|
metricData.setItemValue(ITEM_SALDO, InvoiceUtil.round(lastSaldo + invoiceTotal));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void subtractInvoice(ItemCollection metricData, ItemCollection invoice) {
|
||||||
|
double invoiceTotal = invoice.getItemValueDouble(ITEM_SALDO);
|
||||||
|
if (!"workitem".equals(invoice.getType()) || invoice.getTaskID() >= 5800) {
|
||||||
|
// vorgang ist archiviert oder gelöscht worden => saldo = 0!
|
||||||
|
invoiceTotal = 0.0;
|
||||||
|
}
|
||||||
|
// is the metric new?
|
||||||
|
if (!metricData.hasItem(ITEM_SALDO)) {
|
||||||
|
// init metric with the total value!
|
||||||
|
metricData.setItemValue(ITEM_SALDO, InvoiceUtil.round(invoiceTotal));
|
||||||
|
} else {
|
||||||
|
double lastSaldo = metricData.getItemValueDouble(ITEM_SALDO);
|
||||||
|
metricData.setItemValue(ITEM_SALDO, InvoiceUtil.round(lastSaldo - invoiceTotal));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in a new issue