/* Copyright (c) 2001 - 2011 TOPP - www.openplans.org. All rights reserved. * This code is licensed under the GPL 2.0 license, availible at the root * application directory. */ package org.geoserver.monitor.rest; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintStream; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import org.apache.poi.hssf.usermodel.HSSFCell; import org.apache.poi.hssf.usermodel.HSSFRichTextString; import org.apache.poi.hssf.usermodel.HSSFRow; import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.geoserver.monitor.And; import org.geoserver.monitor.CompositeFilter; import org.geoserver.monitor.Filter; import org.geoserver.monitor.FilterVisitor; import org.geoserver.monitor.FilterVisitorSupport; import org.geoserver.monitor.Monitor; import org.geoserver.monitor.Query; import org.geoserver.monitor.RequestData; import org.geoserver.monitor.RequestDataVisitor; import org.geoserver.monitor.Query.Comparison; import org.geoserver.monitor.Query.SortOrder; import org.geoserver.ows.util.ClassProperties; import org.geoserver.ows.util.OwsUtils; import org.geoserver.rest.ReflectiveResource; import org.geoserver.rest.RestletException; import org.geoserver.rest.format.DataFormat; import org.geoserver.rest.format.MediaTypes; import org.geoserver.rest.format.ReflectiveHTMLFormat; import org.geoserver.rest.format.StreamDataFormat; import org.geotools.data.simple.SimpleFeatureCollection; import org.geotools.data.simple.SimpleFeatureIterator; import org.geotools.feature.type.DateUtil; import org.geotools.util.Converters; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.feature.type.AttributeDescriptor; import org.restlet.data.Form; import org.restlet.data.MediaType; import org.restlet.data.Request; import org.restlet.data.Response; import org.restlet.data.Status; import org.restlet.resource.Representation; import org.restlet.resource.Resource; import freemarker.template.Configuration; public class RequestResource extends ReflectiveResource { public static SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); static { MediaTypes.registerExtension("csv", new MediaType("application/csv")); MediaTypes.registerExtension("zip", MediaType.APPLICATION_ZIP); MediaTypes.registerExtension("xls", MediaType.APPLICATION_EXCEL); } Monitor monitor; public RequestResource(Monitor monitor) { this.monitor = monitor; } @Override protected List<DataFormat> createSupportedFormats(Request request, Response response) { List<DataFormat> formats = super.createSupportedFormats(request, response); formats.add(createCSVFormat(request, response)); formats.add(createZIPFormat(request, response)); formats.add(createExcelFormat(request, response)); return formats; } @Override protected DataFormat createHTMLFormat(Request request, Response response) { return new HTMLFormat(request, response, this, monitor); } String[] getFields(Request request) { String fields = getAttribute("fields"); if (fields != null) { return fields.split(";"); } else { List<String> props = OwsUtils.getClassProperties(RequestData.class).properties(); props.remove("Class"); props.remove("Body"); props.remove("Error"); return props.toArray(new String[props.size()]); } } CSVFormat createCSVFormat(Request request, Response response) { return new CSVFormat(getFields(request), monitor); } ZIPFormat createZIPFormat(Request request, Response response) { String fields = getAttribute("fields"); List<String> props; if (fields == null) { props = OwsUtils.getClassProperties(RequestData.class).properties(); } else { props = Arrays.asList(fields.split(";")); } return new ZIPFormat(props, createCSVFormat(request, response), monitor); } ExcelFormat createExcelFormat(Request request, Response response) { return new ExcelFormat(getFields(request), monitor); } @Override public boolean allowGet() { return true; } @Override public boolean allowDelete() { return true; } @Override protected Object handleObjectGet() throws Exception { String req = getAttribute("request"); if (req == null) { //return a collection Form form = null; if (getRequest().getResourceRef() != null) { form = getRequest().getResourceRef().getQueryAsForm(); } else { form = new Form(); } // date range String from = form.getFirstValue("from"); String to = form.getFirstValue("to"); Query q = new Query().between( from != null ? parseDate(from) : null, to != null ? parseDate(to) : null); //filter String filter = form.getFirstValue("filter"); if (filter != null) { try { parseFilter(filter, q); } catch(Exception e) { throw new RestletException("Error parsing filter " + filter, Status.CLIENT_ERROR_BAD_REQUEST, e); } } //sorting String sortBy; SortOrder sortOrder; String order = form.getFirstValue("order"); if (order != null) { int semi = order.indexOf(';'); if (semi != -1) { String[] split = order.split(";"); sortBy = split[0]; sortOrder = SortOrder.valueOf(split[1]); } else { sortBy = order; sortOrder = SortOrder.ASC; } q.sort(sortBy, sortOrder); } //limit offset String offset = form.getFirstValue("offset"); String count = form.getFirstValue("count"); q.page(offset != null ? Long.parseLong(offset) : null, count != null ? Long.parseLong(count) : null); //live? String live = form.getFirstValue("live"); if (live != null) { if ("yes".equalsIgnoreCase(live) || "true".equalsIgnoreCase(live)) { q.filter("status", Arrays.asList( org.geoserver.monitor.RequestData.Status.RUNNING, org.geoserver.monitor.RequestData.Status.WAITING, org.geoserver.monitor.RequestData.Status.CANCELLING), Comparison.IN); } else { q.filter("status", Arrays.asList( org.geoserver.monitor.RequestData.Status.FINISHED, org.geoserver.monitor.RequestData.Status.FAILED), Comparison.IN); } } return q; } else { //return the individual RequestData data = monitor.getDAO().getRequest(Long.parseLong(req)); if (data == null) { throw new RestletException("No such request" + req, Status.CLIENT_ERROR_NOT_FOUND); } return data; } } Date parseDate(String s) { try { return DATE_FORMAT.parse(s); } catch (ParseException e) { return Converters.convert(s, Date.class); } } void parseFilter(String filter, Query q) { for (String s : filter.split(";")) { if ("".equals(s.trim())) continue; String[] split = s.split(":"); String left = split[0]; Object right = split[2]; if (right.toString().contains(",")) { List list = new ArrayList(); for (String t : right.toString().split(",")) { list.add(parseProperty(left, t)); } right = list; } else { right = parseProperty(left, right.toString()); } q.and(left, right, Comparison.valueOf(split[1])); } } Object parseProperty(String property, String value) { if ("status".equals(property)) { return org.geoserver.monitor.RequestData.Status.valueOf(value); } return value; } @Override protected void handleObjectDelete() throws Exception { String req = getAttribute("request"); if (req == null) { monitor.getDAO().clear(); } } static void handleRequests(Object object, RequestDataVisitor visitor, Monitor monitor) { if (object instanceof Query) { monitor.query((Query)object, visitor); } else { List<RequestData> requests; if (object instanceof List) { requests = (List) object; } else { requests = Collections.singletonList((RequestData)object); } for (RequestData data : requests) { visitor.visit(data, null); } } } static class HTMLFormat extends ReflectiveHTMLFormat { Monitor monitor; protected HTMLFormat(Request request, Response response, Resource resource, Monitor monitor) { super(RequestData.class, request, response, resource); this.monitor = monitor; } @Override public Representation toRepresentation(Object object) { if (object instanceof RequestData) { return super.toRepresentation(object); } //TODO: stream this! final List<RequestData> requests = new ArrayList(); handleRequests(object, new RequestDataVisitor() { public void visit(RequestData data, Object... aggregates) { requests.add(data); } }, monitor); return super.toRepresentation(requests); } @Override protected Configuration createConfiguration(Object data, Class clazz) { Configuration cfg = super.createConfiguration(data, clazz); cfg.setClassForTemplateLoading(RequestResource.class, ""); return cfg; } @Override protected String getTemplateName(Object data) { if (data instanceof RequestData) { return "request.html"; } else { return "requests.html"; } } } static class CSVFormat extends StreamDataFormat { Monitor monitor; String[] fields; protected CSVFormat(String[] fields, Monitor monitor) { super(new MediaType("application/csv")); this.fields = fields; this.monitor = monitor; } @Override protected void write(Object object, OutputStream out) throws IOException { final BufferedWriter w = new BufferedWriter(new OutputStreamWriter(out)); //write out the header StringBuffer sb = new StringBuffer(); for (String fld : fields) { sb.append(fld).append(","); } sb.setLength(sb.length()-1); w.write(sb.append("\n").toString()); handleRequests(object, new RequestDataVisitor() { public void visit(RequestData data, Object... aggregates) { try { writeRequest(data, w); } catch (IOException e) { throw new RuntimeException(e); } } }, monitor); w.flush(); } void writeRequest(RequestData data, BufferedWriter w) throws IOException { StringBuffer sb = new StringBuffer(); for (String fld : fields) { Object val = OwsUtils.get(data, fld); if (val instanceof Date) { val = DateUtil.serializeDateTime((Date)val); } if (val != null) { val = val.toString().replaceAll(",", " ").replaceAll("\n", " "); } sb.append(val).append(","); } sb.setLength(sb.length()-1); sb.append("\n"); w.write(sb.toString()); } @Override protected Object read(InputStream in) throws IOException { return null; } } static class ZIPFormat extends StreamDataFormat { List<String> fields; Monitor monitor; CSVFormat csv; protected ZIPFormat(List<String> fields, CSVFormat csv, Monitor monitor) { super(MediaType.APPLICATION_ZIP); this.fields = fields; this.monitor = monitor; this.csv = csv; } @Override protected void write(Object object, OutputStream out) throws IOException { final ZipOutputStream zout = new ZipOutputStream(out); //create the csv entry zout.putNextEntry(new ZipEntry("requests.csv")); csv.write(object, zout); final boolean body = fields.contains("Body"); final boolean error = fields.contains("Error"); if (object instanceof Query) { monitor.query((Query)object, new RequestDataVisitor() { public void visit(RequestData data, Object... aggregates) { try { writeBodyAndError(data, zout, body, error, true); } catch (IOException e) { throw new RuntimeException(e); } } }); } else if (object instanceof List) { for (RequestData data : (List<RequestData>)object) { writeBodyAndError(data, zout, body, error, true); } } else { writeBodyAndError((RequestData) object, zout, body, error, false); } zout.flush(); zout.close(); } @Override protected Object read(InputStream in) throws IOException { return null; } void writeBodyAndError(RequestData data, ZipOutputStream zout, boolean body, boolean error, boolean postfix) throws IOException { long id = data.getId(); if (body && data.getBody() != null) { //TODO: figure out the proper extension for the body file zout.putNextEntry(new ZipEntry(postfix ? "body_"+id+".txt" : "body.txt")); zout.write(data.getBody()); } if (error && data.getError() != null) { zout.putNextEntry(new ZipEntry(postfix ? "error_"+id+".txt" : "error.txt")); data.getError().printStackTrace(new PrintStream(zout)); } } } static class ExcelFormat extends StreamDataFormat { String[] fields; Monitor monitor; protected ExcelFormat(String[] fields, Monitor monitor) { super(MediaType.APPLICATION_EXCEL); this.fields = fields; this.monitor = monitor; } @Override protected Object read(InputStream in) throws IOException { return null; } @Override protected void write(Object object, OutputStream out) throws IOException { //Create the workbook+sheet HSSFWorkbook wb = new HSSFWorkbook(); final HSSFSheet sheet = wb.createSheet("requests"); //create the header HSSFRow header = sheet.createRow(0); for (int i = 0; i < fields.length; i++) { HSSFCell cell = header.createCell(i); cell.setCellValue(new HSSFRichTextString(fields[i])); } //write out the request handleRequests(object, new RequestDataVisitor() { int i = 1; public void visit(RequestData data, Object... aggregates) { HSSFRow row = sheet.createRow(i++); for (int j = 0; j < fields.length; j++) { HSSFCell cell = row.createCell(j); Object obj = OwsUtils.get(data, fields[j]); if (obj == null) { continue; } if (obj instanceof Date) { cell.setCellValue((Date)obj); } else if (obj instanceof Number) { cell.setCellValue(((Number)obj).doubleValue()); } else { cell.setCellValue(new HSSFRichTextString(obj.toString())); } } } }, monitor); //write to output wb.write(out); } } /** * Converts a query object into one that can be used in the query string of a resource request. */ public static String toQueryString(Query q) { StringBuffer sb = new StringBuffer("?"); if (q.getFromDate() != null) { sb.append("from=").append(DATE_FORMAT.format(q.getFromDate())).append("&"); } if (q.getToDate() != null) { sb.append("to=").append(DATE_FORMAT.format(q.getToDate())).append("&"); } if (q.getFilter() != null) { FilterEncoder fe = new FilterEncoder(); q.getFilter().accept(fe); sb.append("filter=").append(fe.toString()); } return sb.toString(); } //TODO: put these methods in a utility class public static String asString(Date d) { return DATE_FORMAT.format(d); } public static Date toDate(String s) throws ParseException { return DATE_FORMAT.parse(s); } static class FilterEncoder extends FilterVisitorSupport { StringBuffer sb = new StringBuffer(); @Override protected void handleComposite(CompositeFilter f, String type) { if ("OR".equalsIgnoreCase(type)) { throw new IllegalArgumentException("Unable to encode OR filters"); } And and = (And) f; for (Filter fil : and.getFilters()) { fil.accept(this); sb.append(";"); } sb.setLength(sb.length()-1); } @Override protected void handleFilter(Filter f) { sb.append(f.getLeft()).append(":").append(f.getType().name()).append(":"); if (f.getRight() instanceof Collection) { for (Object o : ((Collection)f.getRight())) { sb.append(o).append(","); } sb.setLength(sb.length()-1); } else { sb.append(f.getRight()); } } @Override public String toString() { return sb.toString(); } } }