package org.nishen.alma.toolkit.tasks; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.Date; import java.util.Formatter; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.MediaType; import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVPrinter; import org.nishen.alma.toolkit.entity.report.QueryResultType; import org.nishen.alma.toolkit.entity.report.ReportType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.name.Named; /** * A task to pull a report from Alma Analytics. The row limitation of report exports is * just over 60k records. This means that any report more than 60k rows will need to be * structured in a way that splits the results up across the limitation boundaries. * This can be tedious. It is possible to use the Alma API (Analytics) to fetch a report * from Alma Analytics. This method bypasses the limitation. However, there is additional * work that needs to be done. * * * @author nishen */ public class TaskFetchReport implements Task { private static final Logger log = LoggerFactory.getLogger(TaskFetchReport.class); private static final String TASKNAME = "fetchReport"; private Provider<WebTarget> webTargetProvider; private String title = null; private String csv = null; private String limit = "1000"; @Inject private TaskFetchReport(@Named("app.cmdline") final String[] args, @Named("app.config") final Properties config, @Named("ws.url.alma") Provider<WebTarget> webTargetProvider) { // extract relevant command line arguments if (args != null && args.length > 0) for (int x = 0; x < args.length; x++) { if (args[x].equals("-title")) { if (args.length > (x + 1)) title = args[++x]; } if (args[x].equals("-csv")) { if (args.length > (x + 1)) csv = args[++x]; } else if (args[x].equals("-limit")) { if (args.length > (x + 1)) limit = args[++x]; } } this.webTargetProvider = webTargetProvider; log.debug("initialised tasklistusers"); } @Override public void run() { log.info("executing task: {}", this.getClass().getSimpleName()); // ensure we have a proper number. try { Integer.parseInt(limit); } catch (NumberFormatException nfe) { log.error("option specified is not an integer: {}", limit, nfe); return; } if (title == null) { log.error("title required"); return; } if (csv == null) { log.error("csv required"); return; } // get an instance of the web client. WebTarget target = webTargetProvider.get(); // the 'path' specifies the resource we are targeting e.g. 'users'. // this will result in: https://api-ap.hosted.exlibrisgroup.com/almaws/v1/users // we are also specifying some parameters: limit and offset (apikey preconfigured) // this results in: https://api-ap.hosted.exlibrisgroup.com/almaws/v1/users?apikey=xxx&limit=100&offset=0 String path = title; // path = "/users/1024839030002171_2171_d/User Analysis/Loan Data"; String filter = getFilter(new Date(), new Date()); log.debug("filter:\n{}", filter); target = target.path("analytics/reports").queryParam("limit", limit).queryParam("path", path) .queryParam("filter", filter); try (CSVPrinter printer = new CSVPrinter(new FileWriter(csv), CSVFormat.EXCEL);) { String token = null; boolean finished = false; while (!finished) { if (token != null) target = target.queryParam("token", token); // we avoid dealing with any XML/JSON. We just make the call. ReportType report = target.request(MediaType.APPLICATION_XML_TYPE).get(ReportType.class); QueryResultType result = report.getQueryResult(); finished = result.isIsFinished(); token = result.getResumptionToken(); Element resultXml = (Element) result.getAny(); NodeList rows = resultXml.getElementsByTagName("Row"); if (rows.getLength() == 0) { try { Thread.sleep(5000); } catch (InterruptedException ie) { log.debug("sleep interrupted."); } } for (int x = 0; x < rows.getLength(); x++) { List<String> csvRow = new ArrayList<String>(); Element row = (Element) rows.item(x); NodeList cols = row.getChildNodes(); for (int y = 0; y < cols.getLength(); y++) { Node node = cols.item(y); if (node.getNodeType() == Node.ELEMENT_NODE) { Element col = (Element) cols.item(y); csvRow.add(col.getFirstChild().getNodeValue()); log.debug("column: {}", col.getNodeName()); } } printer.printRecord(csvRow); } } printer.flush(); printer.close(); } catch (IOException ioe) { log.error("unable to write records to csv file: {}", csv, ioe); } } private String getFilter(Date ds, Date de) { StringBuilder result = new StringBuilder(); StringBuilder filter = new StringBuilder(); filter.append("<sawx:expr "); filter.append("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "); filter.append("xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" "); filter.append("xmlns:saw=\"com.siebel.analytics.web/report/v1.1\" "); filter.append("xmlns:sawx=\"com.siebel.analytics.web/expression/v1.1\" "); filter.append("xsi:type=\"sawx:logical\" op=\"and\">"); filter.append("<sawx:expr xsi:type=\"sawx:comparison\" op=\"greaterOrEqual\">"); filter.append("<sawx:expr xsi:type=\"sawx:sqlExpression\">\"Loan Date\".\"Loan Date\"</sawx:expr>"); // filter.append("<sawx:expr xsi:type=\"xsd:date\">%1$tY-%1$tm-%1$td</sawx:expr>"); filter.append("<sawx:expr xsi:type=\"xsd:date\">2016-05-01</sawx:expr>"); filter.append("</sawx:expr>"); filter.append("<sawx:expr xsi:type=\"sawx:comparison\" op=\"less\">"); filter.append("<sawx:expr xsi:type=\"sawx:sqlExpression\">\"Loan Date\".\"Loan Date\"</sawx:expr>"); // filter.append("<sawx:expr xsi:type=\"xsd:date\">%2$tY-%2$tm-%2$td</sawx:expr>"); filter.append("<sawx:expr xsi:type=\"xsd:date\">2016-06-01</sawx:expr>"); filter.append("</sawx:expr>"); filter.append("</sawx:expr>"); Formatter fmt = new Formatter(result); fmt.format(filter.toString(), ds, de); fmt.close(); return result.toString(); } @Override public Map<String, String> getUsageOptions() { Map<String, String> options = new HashMap<String, String>(); options.put("-title \"x\"", "the name of the report to fetch (use double quotes if there are spaces)"); options.put("-csv", "CSV output file"); return options; } public static String getTaskName() { return TASKNAME; } }