Skip to content

Commit 1db629e

Browse files
committed
[KNOWAGE-8936] scheduled dashboard excel export
1 parent 3a3ce91 commit 1db629e

3 files changed

Lines changed: 153 additions & 116 deletions

File tree

knowage-excel-export/src/main/java/it/eng/knowage/engine/api/excel/export/dashboard/DashboardExcelExporter.java

Lines changed: 128 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package it.eng.knowage.engine.api.excel.export.dashboard;
22

33
import it.eng.knowage.commons.multitenant.OrganizationImageManager;
4+
import it.eng.knowage.commons.security.PathTraversalChecker;
45
import it.eng.knowage.engine.api.excel.export.IWidgetExporter;
56
import it.eng.knowage.engine.api.excel.export.dashboard.exporters.DashboardWidgetExporterFactory;
67
import it.eng.spagobi.commons.SingletonConfig;
@@ -12,6 +13,7 @@
1213
import it.eng.spagobi.tenant.TenantManager;
1314
import it.eng.spagobi.utilities.exceptions.SpagoBIRuntimeException;
1415
import lombok.Getter;
16+
import org.apache.commons.codec.binary.Base64;
1517
import org.apache.commons.lang3.StringUtils;
1618
import org.apache.logging.log4j.LogManager;
1719
import org.apache.logging.log4j.Logger;
@@ -30,13 +32,18 @@
3032
import org.json.JSONException;
3133
import org.json.JSONObject;
3234

33-
import java.io.ByteArrayOutputStream;
34-
import java.io.IOException;
35+
import javax.ws.rs.core.UriBuilder;
36+
import java.io.*;
37+
import java.net.URI;
38+
import java.nio.file.Files;
39+
import java.nio.file.Path;
40+
import java.nio.file.Paths;
3541
import java.text.DateFormat;
3642
import java.text.SimpleDateFormat;
3743
import java.util.*;
3844

3945
import static it.eng.knowage.engine.api.excel.export.dashboard.StaticLiterals.EXCEL_ERROR;
46+
import static java.nio.charset.StandardCharsets.UTF_8;
4047
import static org.apache.poi.ss.usermodel.DataConsolidateFunction.*;
4148

4249
public class DashboardExcelExporter extends Common {
@@ -51,6 +58,15 @@ public class DashboardExcelExporter extends Common {
5158
private final JSONObjectUtils jsonObjectUtils;
5259
private final DatastoreUtils datastoreUtils;
5360

61+
// SCHEDULER
62+
private static final String CONFIG_NAME_FOR_EXPORT_SCRIPT_PATH = "internal.nodejs.chromium.export.path";
63+
private static final String SCRIPT_NAME = "cockpit-export-xls.js";
64+
private String role;
65+
private String userUniqueIdentifier = "";
66+
private String requestURL = "";
67+
private String organization = "";
68+
69+
5470
private String imageB64 = "";
5571
private static final String DOCUMENT_NAME = "";
5672
private final boolean isSingleWidgetExport;
@@ -67,7 +83,19 @@ public DashboardExcelExporter(DatastoreUtils datastoreUtils, JSONObject body) {
6783
locale = getLocaleFromBody(body);
6884
jsonObjectUtils = new JSONObjectUtils();
6985
styleProvider = new StyleProvider(jsonObjectUtils);
86+
}
7087

88+
public DashboardExcelExporter(DatastoreUtils datastoreUtils, JSONObject body, String role, String userId, String requestUrl, String organization) {
89+
this.datastoreUtils = datastoreUtils;
90+
this.isSingleWidgetExport = body.optBoolean("exportWidget");
91+
this.body = body;
92+
locale = getLocaleFromBody(body);
93+
jsonObjectUtils = new JSONObjectUtils();
94+
styleProvider = new StyleProvider(jsonObjectUtils);
95+
this.role = role;
96+
this.userUniqueIdentifier = userId;
97+
this.requestURL = requestUrl;
98+
this.organization = organization;
7199
}
72100

73101
private Locale getLocaleFromBody(JSONObject body) {
@@ -81,7 +109,105 @@ private Locale getLocaleFromBody(JSONObject body) {
81109
}
82110

83111
}
112+
public byte[] getScheduledBinaryData(String documentLabel) throws IOException, InterruptedException {
113+
try {
114+
final Path outputDir = Files.createTempDirectory("knowage-xls-exporter-");
115+
116+
String encodedUserId = Base64.encodeBase64String(userUniqueIdentifier.getBytes(UTF_8));
117+
// Script
118+
String cockpitExportScriptPath = SingletonConfig.getInstance()
119+
.getConfigValue(CONFIG_NAME_FOR_EXPORT_SCRIPT_PATH);
120+
Path exportScriptFullPath = Paths.get(cockpitExportScriptPath, SCRIPT_NAME);
121+
122+
if (!Files.isRegularFile(exportScriptFullPath)) {
123+
String msg = String.format(
124+
"Cannot find export script at \"%s\": did you set the correct value for %s configuration?",
125+
exportScriptFullPath, CONFIG_NAME_FOR_EXPORT_SCRIPT_PATH);
126+
IllegalStateException ex = new IllegalStateException(msg);
127+
LOGGER.error(msg, ex);
128+
throw ex;
129+
}
130+
131+
URI url = UriBuilder.fromUri(requestURL)
132+
.replaceQueryParam("outputType_description", "HTML")
133+
.replaceQueryParam("outputType", "HTML")
134+
.replaceQueryParam("role", role)
135+
.replaceQueryParam("organization", organization)
136+
.build();
137+
138+
// avoid sonar security hotspot issue
139+
String cockpitExportExternalProcessName = SingletonConfig.getInstance()
140+
.getConfigValue("KNOWAGE.DASHBOARD.EXTERNAL_PROCESS_NAME");
141+
LOGGER.info("CONFIG label=\"KNOWAGE.DASHBOARD.EXTERNAL_PROCESS_NAME\": " + cockpitExportExternalProcessName);
142+
143+
String stringifiedRequestUrl = url.toString();
144+
// replace localhost:8080 with 127.0.0.1:3000
145+
// stringifiedRequestUrl = stringifiedRequestUrl.replace("localhost:8080", "127.0.0.1:3000");
146+
147+
ProcessBuilder processBuilder = new ProcessBuilder(cockpitExportExternalProcessName, exportScriptFullPath.toString(),
148+
encodedUserId, outputDir.toString(), stringifiedRequestUrl);
149+
150+
setWorkingDirectory(cockpitExportScriptPath, processBuilder);
151+
152+
LOGGER.info("Node complete command line: {}", processBuilder.command());
153+
154+
LOGGER.info("Starting export script");
155+
Process exec = processBuilder.start();
156+
157+
logOutputToCoreLog(exec);
158+
159+
LOGGER.info("Waiting...");
160+
exec.waitFor();
161+
LOGGER.warn("Exit value: {}", exec.exitValue());
162+
163+
// the script creates the resulting xls and saves it to outputFile
164+
Path outputFile = PathTraversalChecker.get(outputDir.toString(), documentLabel + ".xlsx").toPath();
165+
return getByteArrayFromFile(outputFile, outputDir);
166+
} catch (Exception e) {
167+
LOGGER.error("Error during scheduled export execution", e);
168+
throw e;
169+
}
170+
}
171+
private byte[] getByteArrayFromFile(Path excelFile, Path outputDir) {
172+
String fileName = excelFile.toString();
173+
174+
try (FileInputStream fis = new FileInputStream(fileName);
175+
ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
176+
byte[] buf = new byte[1024];
177+
for (int readNum; (readNum = fis.read(buf)) != -1; ) {
178+
// Writes len bytes from the specified byte array starting at offset off to this byte array output stream
179+
bos.write(buf, 0, readNum); // no doubt here is 0
180+
}
181+
return bos.toByteArray();
182+
} catch (Exception e) {
183+
LOGGER.error("Cannot serialize excel file", e);
184+
throw new SpagoBIRuntimeException("Cannot serialize excel file", e);
185+
} finally {
186+
try {
187+
if (Files.isRegularFile(excelFile)) {
188+
Files.delete(excelFile);
189+
}
190+
Files.delete(outputDir);
191+
} catch (Exception e) {
192+
LOGGER.error("Cannot delete temp file", e);
193+
}
194+
}
195+
}
196+
private void logOutputToCoreLog(Process exec) throws IOException {
197+
InputStreamReader isr = new InputStreamReader(exec.getInputStream());
198+
BufferedReader b = new BufferedReader(isr);
199+
String line = null;
200+
LOGGER.warn("Process output");
201+
while ((line = b.readLine()) != null) {
202+
LOGGER.warn(line);
203+
}
204+
}
84205

206+
private void setWorkingDirectory(String cockpitExportScriptPath, ProcessBuilder processBuilder) {
207+
// Required by puppeteer v19
208+
processBuilder.directory(new File(cockpitExportScriptPath));
209+
210+
}
85211
public byte[] getDashboardBinaryData(JSONObject body, boolean isDashboardSingleWidgetExport) {
86212

87213
if (body == null) {

knowage-excel-export/src/main/java/it/eng/knowage/engine/api/excel/export/oldcockpit/ExcelExporter.java

Lines changed: 2 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
import com.google.gson.Gson;
1818
import it.eng.knowage.commons.multitenant.OrganizationImageManager;
19-
import it.eng.knowage.commons.security.PathTraversalChecker;
2019
import it.eng.knowage.engine.api.excel.export.ExporterClient;
2120
import it.eng.knowage.engine.api.excel.export.IWidgetExporter;
2221
import it.eng.knowage.engine.api.excel.export.oldcockpit.exporters.WidgetExporterFactory;
@@ -30,7 +29,6 @@
3029
import it.eng.spagobi.tenant.TenantManager;
3130
import it.eng.spagobi.tools.dataset.bo.IDataSet;
3231
import it.eng.spagobi.utilities.exceptions.SpagoBIRuntimeException;
33-
import org.apache.commons.codec.binary.Base64;
3432
import org.apache.commons.lang3.ObjectUtils;
3533
import org.apache.commons.lang3.StringUtils;
3634
import org.apache.logging.log4j.LogManager;
@@ -43,21 +41,15 @@
4341
import org.json.JSONException;
4442
import org.json.JSONObject;
4543

46-
import javax.ws.rs.core.UriBuilder;
47-
import java.io.*;
48-
import java.net.URI;
49-
import java.nio.file.Files;
50-
import java.nio.file.Path;
51-
import java.nio.file.Paths;
44+
import java.io.ByteArrayOutputStream;
45+
import java.io.IOException;
5246
import java.text.DateFormat;
5347
import java.text.DecimalFormat;
5448
import java.text.SimpleDateFormat;
5549
import java.util.*;
5650
import java.util.regex.Matcher;
5751
import java.util.regex.Pattern;
5852

59-
import static java.nio.charset.StandardCharsets.UTF_8;
60-
6153
/**
6254
* @author Francesco Lucchi (francesco.lucchi@eng.it)
6355
* @author Marco Balestri (marco.balestri@eng.it)
@@ -110,87 +102,6 @@ public String getMimeType() {
110102
return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
111103
}
112104

113-
// used only for scheduled exports
114-
// leverages on an external script that uses chromium to open the cockpit and click on the export button
115-
public byte[] getBinaryData(String documentLabel) throws IOException, InterruptedException {
116-
try {
117-
final Path outputDir = Files.createTempDirectory("knowage-xls-exporter-");
118-
119-
String encodedUserId = Base64.encodeBase64String(userUniqueIdentifier.getBytes(UTF_8));
120-
121-
// Script
122-
String cockpitExportScriptPath = SingletonConfig.getInstance()
123-
.getConfigValue(CONFIG_NAME_FOR_EXPORT_SCRIPT_PATH);
124-
Path exportScriptFullPath = Paths.get(cockpitExportScriptPath, SCRIPT_NAME);
125-
126-
if (!Files.isRegularFile(exportScriptFullPath)) {
127-
String msg = String.format(
128-
"Cannot find export script at \"%s\": did you set the correct value for %s configuration?",
129-
exportScriptFullPath, CONFIG_NAME_FOR_EXPORT_SCRIPT_PATH);
130-
IllegalStateException ex = new IllegalStateException(msg);
131-
LOGGER.error(msg, ex);
132-
throw ex;
133-
}
134-
135-
URI url = UriBuilder.fromUri(requestURL).replaceQueryParam("outputType_description", "HTML")
136-
.replaceQueryParam("outputType", "HTML").build();
137-
138-
// avoid sonar security hotspot issue
139-
String cockpitExportExternalProcessName = SingletonConfig.getInstance()
140-
.getConfigValue("KNOWAGE.DASHBOARD.EXTERNAL_PROCESS_NAME");
141-
LOGGER.info("CONFIG label=\"KNOWAGE.DASHBOARD.EXTERNAL_PROCESS_NAME\": " + cockpitExportExternalProcessName);
142-
143-
ProcessBuilder processBuilder = new ProcessBuilder(cockpitExportExternalProcessName, exportScriptFullPath.toString(),
144-
encodedUserId, outputDir.toString(), url.toString());
145-
146-
setWorkingDirectory(cockpitExportScriptPath, processBuilder);
147-
148-
LOGGER.info("Node complete command line: {}", processBuilder.command());
149-
150-
LOGGER.info("Starting export script");
151-
Process exec = processBuilder.start();
152-
153-
logOutputToCoreLog(exec);
154-
155-
LOGGER.info("Waiting...");
156-
exec.waitFor();
157-
LOGGER.warn("Exit value: {}", exec.exitValue());
158-
159-
// the script creates the resulting xls and saves it to outputFile
160-
Path outputFile = PathTraversalChecker.get(outputDir.toString(), documentLabel + ".xlsx").toPath();
161-
return getByteArrayFromFile(outputFile, outputDir);
162-
} catch (Exception e) {
163-
LOGGER.error("Error during scheduled export execution", e);
164-
throw e;
165-
}
166-
}
167-
168-
private byte[] getByteArrayFromFile(Path excelFile, Path outputDir) {
169-
String fileName = excelFile.toString();
170-
171-
try (FileInputStream fis = new FileInputStream(fileName);
172-
ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
173-
byte[] buf = new byte[1024];
174-
for (int readNum; (readNum = fis.read(buf)) != -1; ) {
175-
// Writes len bytes from the specified byte array starting at offset off to this byte array output stream
176-
bos.write(buf, 0, readNum); // no doubt here is 0
177-
}
178-
return bos.toByteArray();
179-
} catch (Exception e) {
180-
LOGGER.error("Cannot serialize excel file", e);
181-
throw new SpagoBIRuntimeException("Cannot serialize excel file", e);
182-
} finally {
183-
try {
184-
if (Files.isRegularFile(excelFile)) {
185-
Files.delete(excelFile);
186-
}
187-
Files.delete(outputDir);
188-
} catch (Exception e) {
189-
LOGGER.error("Cannot delete temp file", e);
190-
}
191-
}
192-
}
193-
194105
public byte[] getBinaryData(Integer documentId, String documentLabel, String documentName, String templateString, String options)
195106
throws JSONException {
196107
// set document name for exporting
@@ -1533,22 +1444,5 @@ private JSONObject getDatastore(String datasetLabel, Map<String, Object> map, St
15331444
// if pagination is disabled offset = 0, fetchSize = -1
15341445
return getDatastore(datasetLabel, map, selections, 0, -1);
15351446
}
1536-
1537-
private void logOutputToCoreLog(Process exec) throws IOException {
1538-
InputStreamReader isr = new InputStreamReader(exec.getInputStream());
1539-
BufferedReader b = new BufferedReader(isr);
1540-
String line = null;
1541-
LOGGER.warn("Process output");
1542-
while ((line = b.readLine()) != null) {
1543-
LOGGER.warn(line);
1544-
}
1545-
}
1546-
1547-
private void setWorkingDirectory(String cockpitExportScriptPath, ProcessBuilder processBuilder) {
1548-
// Required by puppeteer v19
1549-
processBuilder.directory(new File(cockpitExportScriptPath));
1550-
1551-
}
1552-
15531447
}
15541448

knowagecockpitengine/src/main/java/it/eng/knowage/engine/cockpit/api/page/PageResource.java

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,16 @@
2020
import com.google.common.collect.Iterables;
2121
import it.eng.knowage.engine.api.excel.export.dashboard.DashboardExcelExporter;
2222
import it.eng.knowage.engine.api.excel.export.dashboard.DatastoreUtils;
23-
import it.eng.knowage.engine.api.excel.export.oldcockpit.ExcelExporter;
2423
import it.eng.knowage.engine.cockpit.CockpitEngine;
2524
import it.eng.knowage.engine.cockpit.CockpitEngineInstance;
2625
import it.eng.knowage.engine.cockpit.api.AbstractCockpitEngineResource;
2726
import it.eng.knowage.engine.cockpit.api.export.pdf.nodejs.PdfExporterV2;
2827
import it.eng.knowage.engine.cockpit.api.export.png.PngExporter;
2928
import it.eng.knowage.export.wrapper.beans.RenderOptions;
3029
import it.eng.knowage.export.wrapper.beans.ViewportDimensions;
30+
import it.eng.spago.error.EMFInternalError;
3131
import it.eng.spago.error.EMFUserError;
32+
import it.eng.spago.security.IEngUserProfile;
3233
import it.eng.spagobi.analiticalmodel.document.bo.BIObject;
3334
import it.eng.spagobi.behaviouralmodel.analyticaldriver.bo.BIObjectParameter;
3435
import it.eng.spagobi.commons.SingletonConfig;
@@ -359,18 +360,34 @@ private Response openPagePdfInternal(String pageName)
359360

360361
private Response openPageSpreadsheetInternal(String pageName)
361362
throws IOException, InterruptedException, JSONException {
362-
String requestURL = getRequestUrlForExcelExport(request);
363363

364364
request.setAttribute("template", getIOManager().getTemplateAsString());
365+
String requestURL = getRequestUrlForExcelExport(request);
365366

366-
String userId = request.getParameter("user_id");
367-
Map<String, String[]> parameterMap = request.getParameterMap();
367+
IEngUserProfile profile = (IEngUserProfile) request.getSession().getAttribute(IEngUserProfile.ENG_USER_PROFILE);
368+
Collection<String> roles;
369+
try {
370+
roles = profile.getRoles();
371+
} catch (EMFInternalError emfE) {
372+
LOGGER.error("Error retrieving user roles", emfE);
373+
throw new SpagoBIRuntimeException("Error retrieving user roles", emfE);
374+
}
368375

376+
if (roles.size() != 1) {
377+
LOGGER.error("User has more than one role, cannot export to excel");
378+
throw new SpagoBIRuntimeException("User has more than one role, cannot export to excel");
379+
}
380+
381+
String role = Iterables.get(roles, 0);
382+
383+
String organization = getUserProfile().getOrganization();
384+
385+
String userId = request.getParameter("user_id");
369386
String documentLabel = request.getParameter("DOCUMENT_LABEL");
370387

371-
ExcelExporter excelExporter = new ExcelExporter(userId, parameterMap, requestURL);
388+
DashboardExcelExporter excelExporter = new DashboardExcelExporter(new DatastoreUtils(userId), getIOManager().getTemplateAsJSONObject(), role, userId, requestURL, organization);
372389
String mimeType = excelExporter.getMimeType();
373-
byte[] data = excelExporter.getBinaryData(documentLabel);
390+
byte[] data = excelExporter.getScheduledBinaryData(documentLabel);
374391

375392
return Response.ok(data, mimeType).header("Content-length", Integer.toString(data.length))
376393
.header("Content-Disposition", "attachment; fileName=" + documentLabel + ".xlsx").build();

0 commit comments

Comments
 (0)