Vous pouvez utiliser l'interface REST de QueryBuilder ou créer un service OSGi à l'aide de l'API QueryBuilder pour créer un rapport personnalisé.
Avant d'ajouter un rapport personnalisé, effectuez la procédure de modèle suivante :
Les données utilisées dans les rapports personnalisés doivent être disponibles dans le Rapports de processus. Pour garantir la disponibilité des données, planifiez une tâche cron ou utilisez l’option Sync dans l’interface utilisateur de Rapports de processus.
La requête d’URL (encapsulant la requête souhaitée) doit renvoyer un objet de résultat de requête approprié. Pour créer une requête, vous pouvez utiliser l’interface REST de QueryBuilder pour créer un service OSGi à l’aide de l’API QueryBuilder. Vous pouvez créer des requêtes dynamiques ou statiques.
Créez une interface utilisateur personnalisée pour afficher les résultats. Vous pouvez créer une interface utilisateur autonome ou intégrer un résultat à l’interface utilisateur de Rapports de processus existante.
L’interface REST de CRX QueryBuilder expose les fonctionnalités du créateur de Requêtes de partage d’actifs par le biais d’une API Java et d’une API REST. Découvrez comment utiliser l’interface REST de CRX QueryBuilder avant d’effectuer les étapes suivantes :
Accédez à l’URL https://'[server]:[port]'/lc/bin/querybuilder.json
Créez une requête basée sur la structure de noeud et les propriétés de noeud de l’enregistrement de processus.
Vous pouvez spécifier des paramètres facultatifs pour spécifier le décalage, la limite, les accès et les propriétés. Vous pouvez coder en dur les arguments des rapports statiques et récupérer les paramètres de l’interface utilisateur pour les rapports dynamiques.
Pour récupérer tous les noms de processus, la requête est la suivante :
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
Dans chaque requête, le paramètre de chemin pointe vers l’emplacement de l’enregistrement crx et les caractères sont placés dans une séquence d’échappement conformément à la norme d’URL.
La création d’un service à l’aide de l’API du créateur de Requêtes requiert création et déploiement d’un lot CQ OSGI et à l’aide de l’API du créateur de Requêtes.
Créez un service OSGi avec les annotations appropriées. Pour accéder à QueryBuilder, utilisez :
@Reference(referenceInterface = QueryBuilder.class)
private QueryBuilder queryBuilder;
Créez un groupe de prédicats. Le code permettant de créer un groupe de prédicats est le suivant :
PredicateGroup predicateGroup = new PredicateGroup();
predicateGroup.setAllRequired(true);
Ajoute les prédicats au nouveau predicateGroup. Quelques éléments de prédicat utiles sont JcrBoolPropertyPredicateEvaluator, JcrPropertyPredicateEvaluator, RangePropertyPredicateEvaluator, DateRangePredicateEvaluator, TypePredicateEvaluator.
Pour les rapports statiques, codez en dur les prédicats, tandis que pour les rapports dynamiques, récupérez les prédicats à partir de la requête.
L’exemple de code permettant d’obtenir toutes les instances d’un processus est :
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);
Définissez la Requête à l’aide de la variable prédicateGroup.
Query query = queryBuilder.createQuery(predicateGroup, session);
Obtenez le résultat de la requête.
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();
Itérer le résultat et transformer les résultats au format souhaité. Le code pour envoyer les résultats au format CSV est le suivant :
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());
Utilisez org.apache.felix maven-bundle-plugin
pour créer un lot OSGi pour la servlet.
Déployez le lot sur le serveur CRX.
L’exemple de service suivant comptabilise les instances d’un processus qui se trouve à l’état EN COURS et COMPLETE à la fin de chaque mois, trimestre et année.
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;
}
}
L'exemple de fichier pom.xml
à générer au-dessus du service est :
<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>
Les conditions préalables à la création d’une interface utilisateur distincte pour l’affichage des résultats sont les suivantes : Sling Basics, Création d’un noeud CRX et octroi des privilèges d’accès appropriés.
Créez un noeud CRX au niveau du noeud /apps
et accordez les autorisations d’accès appropriées. (PERM_PROCESS_RAPPORTS_USER)
Définissez le rendu sur le noeud /content
.
Ajoutez des fichiers JSP ou HTML au noeud créé à l’étape 1. Vous pouvez également ajouter des fichiers CSS.
Exemple de noeud avec des fichiers JSP et CSS
Ajoutez du code JavaScript pour début d’un appel Ajax à l’API REST de querybuilder ou à votre service. Ajoutez également les arguments appropriés.
Ajoutez un gestionnaire de succès approprié à l’appel Ajax pour analyser et afficher le résultat. Vous pouvez analyser le résultat dans plusieurs formats (json/csv/user defined) et l’afficher dans un tableau ou dans d’autres formulaires.
(Facultatif) Ajoutez un gestionnaire d’erreurs approprié pour l’appel Ajax.
Un exemple de code JSP qui utilise le service OSGi et l’API QueryBuilder est le suivant :
<%@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>
Les conditions préalables à la création d’une interface utilisateur distincte pour l’affichage des résultats sont les suivantes : Sling Basics, Création d’un noeud CRX et octroi des privilèges d’accès appropriés.
Créez une interface utilisateur distincte.
Créez un noeud enfant nt:unstructured
au noeud /content/process-reporting-runtime/custom-reports
pour chaque rapport enfichable.
Propriétés du noeud
L’interface utilisateur du rapport est intégrée à l’interface utilisateur du Rapports de processus. Après l’intégration de l’interface utilisateur, l’interface utilisateur mise à jour ressemble aux images suivantes :
Interface utilisateur des rapports personnalisés nouvellement ajoutés
Ecran de résultats des rapports personnalisés
Importez le package sample-report-pkg-1.zip
pour intégrer des rapports personnalisés et l’interface utilisateur décrits dans l’article dans l’interface utilisateur de Process Management.