Puoi utilizzare l’interfaccia REST di QueryBuilder o creare un servizio OSGi utilizzando l’API QueryBuilder per creare un rapporto personalizzato.
Prima di aggiungere un rapporto personalizzato, attenersi alla procedura descritta di seguito.
I dati utilizzati nei report personalizzati devono essere disponibili in Process Reporting. Per garantire la disponibilità dei dati, pianificare un job cron o utilizzare Sincronizza nell’interfaccia utente di Process Reporting.
La richiesta URL (che racchiude la query desiderata) deve restituire un oggetto risultato della query appropriato. Per creare una query, puoi utilizzare l’interfaccia REST di QueryBuilder per creare un servizio OSGi utilizzando API QueryBuilder. Puoi creare query dinamiche o statiche.
Crea un’interfaccia utente personalizzata per visualizzare i risultati. Puoi creare un’interfaccia utente autonoma o integrare i risultati con l’interfaccia utente di Process Reporting esistente.
L’interfaccia REST di CRX QueryBuilder espone le funzionalità di Asset Share Query Builder tramite un’API Java e un’API REST. Scopri come utilizzare Interfaccia REST di CRX QueryBuilder, prima di eseguire i passaggi seguenti:
Passa all’URL https://'[server]:[port]'/lc/bin/querybuilder.json
Creare una query basata sulla struttura e sulle proprietà del nodo di archiviazione di Process Reporting.
È possibile specificare parametri facoltativi per specificare offset, limite, hit e proprietà. Puoi codificare gli argomenti per i rapporti statici e recuperare i parametri dall’interfaccia utente per i rapporti dinamici.
Per recuperare tutti i nomi dei processi, la query è:
https://'[server]:[port]'/lc/bin/querybuilder.json?exact=false&p.hits=selective&p.properties=pmProcessTitle&path=%2fcontent%2freporting%2fpm&property=pmNodeType&property.operation=equals&property.value=ProcessType&type=sling%3aFolder
In ogni query, il parametro path punta alla posizione di archiviazione crx e i caratteri sono preceduti dall'escape in base allo standard URL.
I prerequisiti per la creazione di un servizio utilizzando l’API di Query Builder sono creazione e distribuzione del bundle CQ OSGI e utilizzo dell’API Query Builder.
Crea un servizio OSGi con le annotazioni appropriate. Per accedere a QueryBuilder utilizzare:
@Reference(referenceInterface = QueryBuilder.class)
private QueryBuilder queryBuilder;
Crea un gruppo di predicati. Il codice per creare un gruppo di predicati è:
PredicateGroup predicateGroup = new PredicateGroup();
predicateGroup.setAllRequired(true);
Aggiungere predicati al gruppo di predicati appena creato. Alcuni utili costrutti predicato sono JcrBoolPropertyPredicateEvaluator in 5.3, JcrPropertyPredicateEvaluator in 5.3, RangePropertyPredicateEvaluator in 5.3, DateRangePredicateEvaluator in 5.3, e TypePredicateEvaluator in 5.3.
Per i rapporti statici, i predicati vengono codificati; per i rapporti dinamici, invece, i predicati vengono recuperati dalla richiesta.
Di seguito è riportato un esempio di codice per ottenere tutte le istanze di un processo:
Predicate predicate;
//Add the path Constraint
predicate = new Predicate(PathPredicateEvaluator.PATH);
predicate.set(PathPredicateEvaluator.PATH, "/content/reporting/pm"); // should point to the crx path being used to store data
predicate.set(PathPredicateEvaluator.EXACT, "false");
predicateGroup.add(predicate);
//type nt:unstructured
predicate = new Predicate(TypePredicateEvaluator.TYPE);
predicate.set(TypePredicateEvaluator.TYPE, "nt:unstructured");
predicateGroup.add(predicate);
//NodeType: Process Instance
predicate = new Predicate(JcrPropertyPredicateEvaluator.PROPERTY);
predicate.set(JcrPropertyPredicateEvaluator.PROPERTY, "pmNodeType");
predicate.set(JcrPropertyPredicateEvaluator.OPERATION, JcrPropertyPredicateEvaluator.OP_EQUALS);
predicate.set(JcrPropertyPredicateEvaluator.VALUE, "ProcessInstance");
predicateGroup.add(predicate);
//processName
predicate = new Predicate(JcrPropertyPredicateEvaluator.PROPERTY);
predicate.set(JcrPropertyPredicateEvaluator.PROPERTY, "pmProcessName");
predicate.set(JcrPropertyPredicateEvaluator.OPERATION, JcrPropertyPredicateEvaluator.OP_EQUALS);
predicate.set(JcrPropertyPredicateEvaluator.VALUE, processName); //processName variable stores the name of the process whose instances need to be searched
predicateGroup.add(predicate);
Definire la query utilizzando il predicateGroup.
Query query = queryBuilder.createQuery(predicateGroup, session);
Ottenere il risultato della query.
query.setStart(offset); // hardcode or fetch from request
if(hits == -1) // hardcode or fetch from request
hits = 0;
query.setHitsPerPage(hits);
SearchResult searchResult = query.getResult();
Iterate il risultato e trasformate i risultati nel formato desiderato. Il codice per inviare i risultati in formato CSV è:
Iterator<Node> iter = searchResult.getNodes();
while(iter.hasNext()) {
Node node = iter.next();
row = new StringBuilder();
for (String property : includeProperties) { // the properties of the node which needs to be returned, or one can return all the properties too.
try {
row.append(node.getProperties(property).nextProperty().getString() + COMMA_SEPARATOR);
} catch (NoSuchElementException e) {
//Adding separator for no value
row.append(COMMA_SEPARATOR);
} catch (RepositoryException e) {
e.printStackTrace();
}
}
row.deleteCharAt(row.lastIndexOf(COMMA_SEPARATOR));
row.append(NEW_LINE);
out.write(row.toString().getBytes());
Utilizza il org.apache.felix maven-bundle-plugin
per creare un bundle OSGi per il servlet.
Distribuisci il bundle sul server CRX.
Nell'esempio di servizio riportato di seguito vengono conteggiate le istanze di un processo in IN ESECUZIONE e COMPLETATO stato alla fine di ogni mese, trimestre e anno.
package custom.reporting.service;
import java.text.DateFormatSymbols;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.jcr.Node;
import javax.jcr.Session;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import com.day.cq.search.Predicate;
import com.day.cq.search.PredicateGroup;
import com.day.cq.search.Query;
import com.day.cq.search.QueryBuilder;
import com.day.cq.search.eval.JcrPropertyPredicateEvaluator;
import com.day.cq.search.eval.PathPredicateEvaluator;
import com.day.cq.search.eval.TypePredicateEvaluator;
import com.day.cq.search.result.SearchResult;
@Component(metatype = true, immediate = true, label = "PeriodicProcessVolume", description = "Service for supporting cutom reports pluggable to Process Reporting.")
@Service(value = PeriodicProcessVolume.class)
public class PeriodicProcessVolume {
private static String[] monthNameList = new DateFormatSymbols().getMonths();
private static String[] quaterNameList = { "I", "II", "III", "IV" };
private final Map<Integer, Map<Integer, Long[]>> monthly = new HashMap<Integer, Map<Integer, Long[]>>();
private final Map<Integer, Map<Integer, Long[]>> quaterly = new HashMap<Integer, Map<Integer, Long[]>>();
private final Map<Integer, Long[]> yearly = new HashMap<Integer, Long[]>();
@Reference(referenceInterface = QueryBuilder.class)
private QueryBuilder queryBuilder;
private void addConstraints(PredicateGroup predicateGroup, String processName) {
Predicate predicate;
//Add the path Constraint
predicate = new Predicate(PathPredicateEvaluator.PATH);
predicate.set(PathPredicateEvaluator.PATH, "/content/reporting/pm");
predicate.set(PathPredicateEvaluator.EXACT, "false");
predicateGroup.add(predicate);
//type nt:unstructured
predicate = new Predicate(TypePredicateEvaluator.TYPE);
predicate.set(TypePredicateEvaluator.TYPE, "nt:unstructured");
predicateGroup.add(predicate);
//NodeType: Process Instance
predicate = new Predicate(JcrPropertyPredicateEvaluator.PROPERTY);
predicate.set(JcrPropertyPredicateEvaluator.PROPERTY, "pmNodeType");
predicate.set(JcrPropertyPredicateEvaluator.OPERATION, JcrPropertyPredicateEvaluator.OP_EQUALS);
predicate.set(JcrPropertyPredicateEvaluator.VALUE, "ProcessInstance");
predicateGroup.add(predicate);
//processName
if (processName != null) {
predicate = new Predicate(JcrPropertyPredicateEvaluator.PROPERTY);
predicate.set(JcrPropertyPredicateEvaluator.PROPERTY, "pmProcessName");
predicate.set(JcrPropertyPredicateEvaluator.OPERATION, JcrPropertyPredicateEvaluator.OP_EQUALS);
predicate.set(JcrPropertyPredicateEvaluator.VALUE, processName);
predicateGroup.add(predicate);
}
}
private Long[] setFrequency(Long[] frequency, int index) {
if (frequency == null) {
frequency = new Long[2];
frequency[0] = 0L;
frequency[1] = 0L;
}
frequency[index] = frequency[index] + 1L;
return frequency;
}
public void populateValues(Session session, String processName) {
PredicateGroup predicateGroup = new PredicateGroup();
predicateGroup.setAllRequired(true);
try {
addConstraints(predicateGroup, processName);
long batchSize = 10000L;
long start = 0l;
while (true) {
Query query = queryBuilder.createQuery(predicateGroup, session);
query.setStart(start);
query.setHitsPerPage(batchSize);
SearchResult searchResult = query.getResult();
Iterator<Node> itr = searchResult.getNodes();
long length = 0;
while (itr.hasNext()) {
length++;
Node n = itr.next();
Calendar calender = n.getProperty("pmCreateTime").getDate();
String status = n.getProperty("pmStatus").getString();
int index = 0;
if ("COMPLETE".equals(status)) {
index = 1;
} else if ("RUNNING".equals(status)) {
index = 0;
} else {
continue;
}
int month = calender.get(Calendar.MONTH);
int year = calender.get(Calendar.YEAR);
int quater;
if (month < 3) {
quater = 1;
} else if (month < 6) {
quater = 2;
} else if (month < 9) {
quater = 3;
} else {
quater = 4;
}
Long frequency[];
Map<Integer, Long[]> yearMonthMap = this.monthly.get(year);
if (yearMonthMap == null) {
yearMonthMap = new HashMap<Integer, Long[]>();
}
frequency = yearMonthMap.get(month);
frequency = setFrequency(frequency, index);
yearMonthMap.put(month, frequency);
this.monthly.put(year, yearMonthMap);
Map<Integer, Long[]> yearQuaterMap = this.quaterly.get(year);
if (yearQuaterMap == null) {
yearQuaterMap = new HashMap<Integer, Long[]>();
}
frequency = yearQuaterMap.get(quater);
frequency = setFrequency(frequency, index);
yearQuaterMap.put(quater, frequency);
this.quaterly.put(year, yearQuaterMap);
frequency = this.yearly.get(year);
frequency = setFrequency(frequency, index);
this.yearly.put(year, frequency);
}
if (length < batchSize) {
break;
} else {
start = start + batchSize;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
public Map<String, Long[]> getMonthly() {
Map<String, Long[]> result = new LinkedHashMap<String, Long[]>();
SortedSet<Integer> years = new TreeSet<Integer>(monthly.keySet());
for (Integer year : years) {
Map<Integer, Long[]> yearMonthMap = monthly.get(year);
SortedSet<Integer> months = new TreeSet<Integer>(yearMonthMap.keySet());
for (Integer month : months) {
String str = monthNameList[month] + " " + year;
result.put(str, yearMonthMap.get(month));
}
}
return result;
}
public Map<String, Long[]> getQuaterly() {
Map<String, Long[]> result = new LinkedHashMap<String, Long[]>();
SortedSet<Integer> years = new TreeSet<Integer>(quaterly.keySet());
for (Integer year : years) {
Map<Integer, Long[]> quaterMonthMap = quaterly.get(year);
SortedSet<Integer> quaters = new TreeSet<Integer>(quaterMonthMap.keySet());
for (Integer quater : quaters) {
String str = quaterNameList[quater - 1] + " " + year;
result.put(str, quaterMonthMap.get(quater));
}
}
return result;
}
public Map<Integer, Long[]> getYearly() {
return yearly;
}
}
Il campione pom.xml
il file da generare sopra il servizio è:
<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- ====================================================================== -->
<!-- P R O J E C T D E S C R I P T I O N -->
<!-- ====================================================================== -->
<groupId>com.custom</groupId>
<artifactId>sample-report-core</artifactId>
<packaging>bundle</packaging>
<name>PR Sample Report</name>
<description>Bundle providing support for a custom report pluggable to process reporting.</description>
<version>1</version>
<!-- ====================================================================== -->
<!-- B U I L D D E F I N I T I O N -->
<!-- ====================================================================== -->
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>2.3.7</version>
<extensions>true</extensions>
<configuration>
<instructions>
<Bundle-Category>sample-report</Bundle-Category>
<Export-Package>
custom.reporting.service.*;
</Export-Package>
</instructions>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-scr-plugin</artifactId>
<version>1.11.0</version>
<executions>
<execution>
<id>generate-scr-scrdescriptor</id>
<goals>
<goal>scr</goal>
</goals>
<configuration>
<!-- Private service properties for all services. -->
<properties>
<service.vendor>Sample Report</service.vendor>
</properties>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<!-- ====================================================================== -->
<!-- D E P E N D E N C I E S -->
<!-- ====================================================================== -->
<dependencies>
<dependency>
<groupId>com.day.cq</groupId>
<artifactId>cq-search</artifactId>
<version>5.6.4</version>
</dependency>
<dependency>
<groupId>javax.jcr</groupId>
<artifactId>jcr</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.scr.annotations</artifactId>
<version>1.9.0</version>
</dependency>
</dependencies>
</project>
I prerequisiti per creare un’interfaccia utente separata per la visualizzazione dei risultati sono Nozioni di base su Sling in 5.6.1, Creazione di un nodo CRX e fornendo adeguate privilegi di accesso.
Crea un nodo CRX in corrispondenza di /apps
e concedere le autorizzazioni di accesso appropriate. (PERM_PROCESS_REPORTING_USER)
Definisci il renderer in corrispondenza di /content
nodo.
Aggiungi file JSP o HTML al nodo creato nel passaggio 1. Puoi anche aggiungere file CSS.
Un nodo di esempio con file JSP e CSS
Aggiungi il codice JavaScript per avviare una chiamata Ajax all’API REST di querybuilder o al tuo servizio. Aggiungere inoltre gli argomenti appropriati.
Aggiungi un handler di successo appropriato alla chiamata Ajax per analizzare e visualizzare il risultato. Puoi analizzare il risultato in più formati (definiti da json/csv/utente) e visualizzarlo in una tabella o in altre forme.
(Facoltativo) Aggiungi un gestore degli errori appropriato alla chiamata Ajax.
Un esempio di codice JSP che utilizza sia il servizio OSGi che l’API QueryBuilder è:
<%@taglib prefix="sling" uri="https://sling.apache.org/taglibs/sling/1.0"%>
<%request.setAttribute("silentAuthor", new Boolean(true));%>
<%@include file="/libs/foundation/global.jsp"%>
<%@ page import="java.util.Map,
java.util.Set,
com.adobe.idp.dsc.registry.service.ServiceRegistry,
javax.jcr.Session,
org.apache.sling.api.resource.ResourceResolver,
custom.reporting.service.PeriodicProcessVolume"%>
<%
response.setContentType("text/html");
response.setCharacterEncoding("utf-8");
%><!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="/lc/apps/sample-report-process-reporting/custom-reports/periodicProcessVolume/style.css">
<title>REPORT Monthly / Qaterly / Yearly</title>
<script type="text/javascript">
<%
slingResponse.setCharacterEncoding("utf-8");
ResourceResolver resolver = slingRequest.getResourceResolver();
String processName = slingRequest.getParameter("processName");
Session session = resolver.adaptTo(Session.class);
custom.reporting.service.PeriodicProcessVolume periodicProcessVolume = sling.getService(custom.reporting.service.PeriodicProcessVolume.class);
periodicProcessVolume.populateValues(session, processName);
if (processName == null) {
processName = "All";
}
%>
var lineSeprator = "<td class='seprator'>----------------</td>";
var tableEnder = "<tr>" + lineSeprator + lineSeprator + lineSeprator + "</tr>";
var tableColHeader = "<td class='colHead colNum'>Running</td>";
tableColHeader += "<td class='colHead colNum'>Complete</td></tr>";
tableColHeader += tableEnder;
var monthly = "<table><tr><td class='colHead colStr'>Month</td>";
monthly += tableColHeader;
<%
Map<String, Long[]> monthlyMap = periodicProcessVolume.getMonthly();
Set<String> monthKeys = monthlyMap.keySet();
for (String key: monthKeys) {
Long[] frequencies = monthlyMap.get(key);
%>
monthly += "<tr><td class='colStr'> <%= key %> </td>";
monthly += "<td class='colNum'> <%= frequencies[0] %> </td>";
monthly += "<td class='colNum'> <%= frequencies[1] %> </td></tr>";
<%
}
%>
monthly += tableEnder;
var quaterly = "<table><tr><td class='colHead colStr'>Quater</td>";
quaterly += tableColHeader;
<%
Map<String, Long[]> quaterMap = periodicProcessVolume.getQuaterly();
Set<String> quaterKeys = quaterMap.keySet();
for (String key: quaterKeys) {
Long[] frequencies = quaterMap.get(key);
%>
quaterly += "<tr><td class='colStr'> <%= key %> </td>";
quaterly += "<td class='colNum'> <%= frequencies[0] %> </td>";
quaterly += "<td class='colNum'> <%= frequencies[1] %> </td></tr>";
<%
}
%>
quaterly += tableEnder;
var yearly = "<table><tr><td class='colHead colStr'>Year</td>";
yearly += tableColHeader;
<%
Map<Integer, Long[]> yearMap = periodicProcessVolume.getYearly();
Set<Integer> yearKeys = yearMap.keySet();
for (Integer key: yearKeys) {
Long[] frequencies = yearMap.get(key);
%>
yearly += "<tr><td class='colStr'> <%= key %> </td>";
yearly += "<td class='colNum'> <%= frequencies[0] %> </td>";
yearly += "<td class='colNum'> <%= frequencies[1] %> </td></tr>";
<%
}
%>
yearly += tableEnder;
function reloadFrame(value) {
if (value === '-1') {
window.location = "/lc/content/process-reporting-runtime/custom-reports/periodicProcessVolume.html";
} else {
window.location = "/lc/content/process-reporting-runtime/custom-reports/periodicProcessVolume.html?processName=" + value;
}
}
function populateTable(selection) {
if (selection === 0) {
document.getElementById('tableHeading').innerHTML = 'Monthly';
document.getElementById('volumeTable').innerHTML = monthly;
} else if (selection === 1) {
document.getElementById('tableHeading').innerHTML = 'Quaterly';
document.getElementById('volumeTable').innerHTML = quaterly;
} else {
document.getElementById('tableHeading').innerHTML = 'Yearly';
document.getElementById('volumeTable').innerHTML = yearly;
}
}
function fetchProcesses() {
var xmlhttp = new XMLHttpRequest(),
request = '';
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
var responseText,
response,
items,
hits = [],
responseSize = 0,
processName,
selectedIndex = 0,
comboBox;
responseText = xmlhttp.responseText;
if (responseText !== undefined && responseText !== null) {
response = JSON.parse(responseText);
responseSize = response.results;
hits = response.hits;
}
items = "<option value='-1'>All</option>";
for(var i = 0; i < responseSize; i++) {
processName = hits[i].pmProcessTitle;
if (processName === '<%= processName %>') {
selectedIndex = i + 1;
}
items += "<option value='" + processName + "'>" + processName + "</option>"
}
comboBox = document.getElementById('processSelection');
comboBox.innerHTML = items;
comboBox.selectedIndex = selectedIndex;
}
};
request = "/lc/bin/querybuilder.json?";
request += "exact=false&";
request += "p.hits=selective&";
request += "p.properties=pmProcessTitle&";
request += "path=%2fcontent%2freporting%2fpm&";
request += "property=pmNodeType&";
request += "property.operation=equals&";
request += "property.value=ProcessType&";
request += "type=sling%3aFolder";
xmlhttp.open("POST", request, true);
xmlhttp.setRequestHeader("Content-type","application/json");
xmlhttp.send();
}
</script>
</head>
<body onLoad="fetchProcesses();populateTable(0);">
Process:
<select id="processSelection" onchange="reloadFrame(this.value);"></select>
    Period Interval:
<select name="periodSelection" onchange="populateTable(this.selectedIndex);">
<option value="1">Monthly</option>
<option value="2">Quaterly</option>
<option value="3">Yearly</option>
</select>
<br> <br> <br> <br>
<div class="inline"> Process:   <b><%= processName %></b>     Period:   </div> <b> <div id="tableHeading" class="inline"> </div> </b>
<br><br>
<div id="volumeTable"> </div>
</body>
</html>
I prerequisiti per creare un’interfaccia utente separata per la visualizzazione dei risultati sono Nozioni di base su Sling, Creazione di un nodo CRX e fornendo adeguate privilegi di accesso.
Crea un’interfaccia utente separata.
Creare un elemento figlio nt:unstructured
nodo in corrispondenza di /content/process-reporting-runtime/custom-reports
per ogni rapporto inseribile.
Proprietà del nodo
L’interfaccia utente del report è integrata nell’interfaccia utente di Process Reporting. Dopo aver integrato l’interfaccia utente, questa viene aggiornata in modo simile alle immagini seguenti:
Interfaccia utente dei rapporti personalizzati appena aggiunti
Schermata dei risultati dei rapporti personalizzati
Importa sample-report-pkg-1.zip
per integrare i rapporti personalizzati e l’interfaccia utente descritti nell’articolo nell’interfaccia utente per la gestione dei processi.