teichmann@5863: /* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde teichmann@5863: * Software engineering by Intevation GmbH teichmann@5863: * teichmann@5994: * This file is Free Software under the GNU AGPL (>=v3) teichmann@5863: * and comes with ABSOLUTELY NO WARRANTY! Check out the teichmann@5994: * documentation coming with Dive4Elements River for details. teichmann@5863: */ teichmann@5863: teichmann@5831: package org.dive4elements.river.exports; ingo@391: ingo@391: import java.io.IOException; ingo@391: import java.io.OutputStream; ingo@391: import java.io.OutputStreamWriter; ingo@391: felix@2284: import java.text.NumberFormat; felix@2284: ingo@391: import org.w3c.dom.Document; ingo@391: ingo@391: import org.apache.log4j.Logger; ingo@391: ingo@391: import au.com.bytecode.opencsv.CSVWriter; ingo@391: teichmann@5831: import org.dive4elements.artifacts.Artifact; teichmann@5831: import org.dive4elements.artifacts.CallContext; ingo@695: teichmann@5831: import org.dive4elements.artifactdatabase.state.ArtifactAndFacet; teichmann@5831: import org.dive4elements.artifactdatabase.state.Settings; ingo@445: teichmann@5831: import org.dive4elements.artifacts.common.ArtifactNamespaceContext; teichmann@5831: import org.dive4elements.artifacts.common.utils.XMLUtils; ingo@416: teichmann@5831: import org.dive4elements.river.artifacts.resources.Resources; teichmann@5867: import org.dive4elements.river.collections.D4EArtifactCollection; teichmann@5831: teichmann@6905: import org.dive4elements.river.themes.ThemeDocument; teichmann@5831: import org.dive4elements.river.utils.Formatter; felix@2284: ingo@391: ingo@391: /** tom@8856: * Abstract exporter that implements some basic methods for exporting data of ingo@391: * artifacts. ingo@391: * ingo@391: * @author Ingo Weinzierl ingo@391: */ ingo@391: public abstract class AbstractExporter implements OutGenerator { ingo@391: teichmann@8202: /** The log used in this exporter.*/ teichmann@8202: private static Logger log = Logger.getLogger(AbstractExporter.class); ingo@391: sascha@3223: /* XXX: Why does AbstractExporter do not implement FacetTypes? */ sascha@3223: public static String FIX_PARAMETERS = "fix_parameters"; ingo@391: felix@1160: /** The name of the CSV facet which triggers the CSV creation. */ ingo@391: public static final String FACET_CSV = "csv"; ingo@391: raimund@2176: /** The name of the PDF facet which triggers the PDF creation. */ raimund@2176: public static final String FACET_PDF = "pdf"; raimund@2176: felix@1160: /** The default charset for the CSV export. */ ingo@391: public static final String DEFAULT_CSV_CHARSET = "UTF-8"; ingo@391: felix@1160: /** The default separator for the CSV export. */ felix@7035: public static final char DEFAULT_CSV_SEPARATOR = ';'; ingo@391: tom@7937: public static final String START_META_CHAR = "#"; tom@7937: felix@1160: /** XPath that points to the desired export facet. */ ingo@445: public static final String XPATH_FACET = "/art:action/@art:type"; ingo@445: teichmann@7077: /** The out name to serve. */ teichmann@7077: protected String outName; teichmann@7077: felix@1160: /** The document of the incoming out() request. */ ingo@391: protected Document request; ingo@391: felix@1160: /** The output stream where the data should be written to. */ ingo@391: protected OutputStream out; ingo@391: felix@1160: /** The CallContext object. */ ingo@391: protected CallContext context; ingo@391: felix@1160: /** The selected facet. */ ingo@391: protected String facet; ingo@391: ingo@3422: /** The collection.*/ teichmann@5867: protected D4EArtifactCollection collection; ingo@3422: felix@1160: /** The master artifact. */ ingo@412: protected Artifact master; ingo@412: felix@5111: private NumberFormat kmFormat; felix@5111: felix@5111: private NumberFormat wFormat; felix@5111: felix@5111: private NumberFormat qFormat; felix@5111: gernotbelger@8862: private NumberFormat meanBedHeightFormat; gernotbelger@8862: gernotbelger@8862: private NumberFormat tkhFormat; gernotbelger@8862: gernotbelger@8862: private NumberFormat flowDepthFormat; gernotbelger@8862: gernotbelger@8862: private NumberFormat w2Format; ingo@391: ingo@391: /** ingo@391: * Concrete subclasses need to use this method to write their special data ingo@391: * objects into the CSV document. ingo@391: * ingo@391: * @param writer The CSVWriter. ingo@391: */ ingo@2792: protected abstract void writeCSVData(CSVWriter writer) throws IOException; ingo@391: ingo@391: ingo@391: /** tom@7937: * Write lines of informative content to CSV file. tom@7937: * Usually this will be done above the column headers from within tom@7937: * the implementation of writeCSVData in concret subclasses. tom@7937: * tom@7937: * @param writer The CSVWriter tom@7937: * @param infolines Array of Strings with informative content. tom@7937: * Each will be written to a separate line prefixed with START_META_CHAR. tom@7937: */ tom@7937: protected void writeCSVInfo(CSVWriter writer, String[] infolines) { tom@7937: String[] metaline = new String[1]; tom@7937: tom@7937: for (String infoline: infolines) { tom@7937: metaline[0] = START_META_CHAR + " " + infoline; tom@7937: writer.writeNext(metaline); tom@7937: } tom@7937: tom@7937: } tom@7937: tom@7937: /** raimund@2176: * Concrete subclasses need to use this method to write their special data raimund@2176: * objects into the PDF document. raimund@2176: */ raimund@2176: protected abstract void writePDF(OutputStream out); raimund@2176: raimund@2176: raimund@2176: /** ingo@391: * This method enables concrete subclasses to collected its own special ingo@391: * data. ingo@391: * felix@3270: * @param data The artifact that stores the data that has to be ingo@391: * exported. ingo@391: */ sascha@701: protected abstract void addData(Object data); ingo@391: teichmann@7087: public void setup(Object config) { teichmann@8202: log.debug("AbstractExporter.setup"); teichmann@7037: } teichmann@7037: felix@3270: sascha@710: @Override teichmann@7077: public void init( teichmann@7077: String outName, teichmann@7077: Document request, teichmann@7077: OutputStream out, teichmann@7077: CallContext context teichmann@7077: ) { teichmann@8202: log.debug("AbstractExporter.init"); ingo@391: teichmann@7077: this.outName = outName; ingo@391: this.request = request; ingo@391: this.out = out; ingo@391: this.context = context; ingo@391: } ingo@391: ingo@391: sascha@710: @Override ingo@412: public void setMasterArtifact(Artifact master) { ingo@412: this.master = master; ingo@412: } ingo@412: felix@5102: /** Get the callcontext that this exporter has been initialized felix@5102: * with. */ felix@5102: public CallContext getCallContext() { felix@5102: return this.context; felix@5102: } felix@5102: ingo@412: ingo@3422: @Override teichmann@5867: public void setCollection(D4EArtifactCollection collection) { ingo@3422: this.collection = collection; ingo@3422: } ingo@3422: ingo@3422: ingo@391: /** ingo@391: * This doOut() just collects the data of multiple artifacts. Therefore, it ingo@391: * makes use of the addData() method which enables concrete subclasses to ingo@391: * store its data on its own. The real output creation takes place in the ingo@391: * concrete generate() methods. ingo@391: * felix@3270: * @param artifactFacet The artifact and facet. felix@3270: * The facet to add - NOTE: the facet needs to fit to the first ingo@391: * facet inserted into this exporter. Otherwise this artifact/facet is ingo@391: * skipped. ingo@391: * @param attr The attr document. ingo@391: */ sascha@710: @Override ingo@1684: public void doOut( felix@1944: ArtifactAndFacet artifactFacet, teichmann@6905: ThemeDocument attr, felix@1944: boolean visible ingo@1684: ) { felix@1944: String name = artifactFacet.getFacetName(); ingo@391: teichmann@8202: log.debug("AbstractExporter.doOut: " + name); ingo@695: ingo@695: if (!isFacetValid(name)) { teichmann@8202: log.warn("Facet '" + name + "' not valid. No output created!"); ingo@391: return; ingo@391: } ingo@391: ingo@2038: addData(artifactFacet.getData(context)); ingo@391: } ingo@391: ingo@391: ingo@391: /** ingo@391: * Generates an export based on a specified facet. ingo@391: */ sascha@710: @Override ingo@391: public void generate() ingo@391: throws IOException ingo@391: { teichmann@8202: log.debug("AbstractExporter.generate"); ingo@391: sascha@3217: if (facet == null) { sascha@3217: throw new IOException("invalid (null) facet for exporter"); sascha@3217: } sascha@3217: sascha@3217: if (facet.equals(FACET_CSV)) { ingo@391: generateCSV(); ingo@391: } sascha@3217: else if (facet.equals(FACET_PDF)) { raimund@2176: generatePDF(); raimund@2176: } ingo@391: else { sascha@3223: throw new IOException( sascha@3223: "invalid facet for exporter: '" + facet + "'"); ingo@391: } ingo@391: } ingo@391: ingo@391: ingo@391: /** ingo@391: * Determines if the desired facet is valid for this exporter. If no facet ingo@391: * is currently set, facet is set. ingo@391: * ingo@391: * @param facet The desired facet. ingo@391: * ingo@391: * @return true, if facet is valid, otherwise false. ingo@391: */ ingo@391: protected boolean isFacetValid(String facet) { tom@8856: log.debug("AbstractExporter.isFacetValid : " tom@8856: + facet + " (" + getFacet() + ")" ); ingo@391: ingo@445: String thisFacet = getFacet(); ingo@445: ingo@445: if (thisFacet == null || thisFacet.length() == 0) { ingo@391: return false; ingo@391: } ingo@445: else if (facet == null || facet.length() == 0) { ingo@445: return false; ingo@391: } ingo@391: else { ingo@445: return thisFacet.equals(facet); ingo@391: } ingo@391: } ingo@391: ingo@391: ingo@445: /** ingo@445: * Returns the name of the desired facet. ingo@445: * ingo@445: * @return the name of the desired facet. ingo@445: */ ingo@445: protected String getFacet() { ingo@445: if (facet == null) { ingo@445: facet = getFacetFromRequest(); ingo@445: } ingo@445: ingo@445: return facet; ingo@445: } ingo@445: ingo@445: ingo@445: /** ingo@445: * Extracts the name of the requested facet from request document. ingo@445: * ingo@445: * @return the name of the requested facet. ingo@445: */ ingo@445: protected String getFacetFromRequest() { ingo@445: return XMLUtils.xpathString( ingo@445: request, XPATH_FACET, ArtifactNamespaceContext.INSTANCE); ingo@445: } ingo@445: gernotbelger@8862: protected String msg(final String key) { tom@7936: return Resources.getMsg(context.getMeta(), key, key); tom@7936: } ingo@416: gernotbelger@8862: protected String msg(String key, final String def) { ingo@416: return Resources.getMsg(context.getMeta(), key, def); ingo@416: } ingo@416: gernotbelger@8936: protected String msg(String key, Object[] args) { tom@8179: return Resources.getMsg(context.getMeta(), key, key, args); tom@8179: } tom@8179: gernotbelger@8936: protected String msg(String key, String def, Object[] args) { felix@5133: return Resources.getMsg(context.getMeta(), key, def, args); felix@5133: } gernotbelger@8936: gernotbelger@8936: protected String msgVarg(final String key, final Object... args) { gernotbelger@8936: return Resources.getMsg(context.getMeta(), key, key, args); gernotbelger@8936: } gernotbelger@8936: gernotbelger@8928: /** gernotbelger@8928: * Formats header with unit: msg [unit] gernotbelger@8928: */ gernotbelger@8928: protected final String msgUnit(final String key, final String unit) { gernotbelger@8928: final String msg = msg(key); gernotbelger@8928: return String.format("%s [%s]", msg, unit); gernotbelger@8928: } ingo@416: ingo@391: /** ingo@391: * This method starts CSV creation. It makes use of writeCSVData() which has ingo@391: * to be implemented by concrete subclasses. ingo@391: */ ingo@391: protected void generateCSV() ingo@391: throws IOException ingo@391: { teichmann@8202: log.info("AbstractExporter.generateCSV"); ingo@391: rrenkert@4910: char quote = '"'; rrenkert@4910: char escape = '\\'; rrenkert@4910: ingo@391: CSVWriter writer = new CSVWriter( ingo@391: new OutputStreamWriter( ingo@391: out, ingo@391: DEFAULT_CSV_CHARSET), rrenkert@4910: DEFAULT_CSV_SEPARATOR, quote, escape, "\r\n"); ingo@391: ingo@391: writeCSVData(writer); ingo@391: ingo@391: writer.close(); ingo@391: } ingo@1979: ingo@1979: ingo@1979: /** raimund@2176: * This method starts PDF creation. raimund@2176: */ raimund@2176: protected void generatePDF() raimund@2176: throws IOException raimund@2176: { teichmann@8202: log.info("AbstractExporter.generatePDF"); raimund@2176: writePDF(this.out); raimund@2176: } raimund@2176: raimund@2176: raimund@2176: /** ingo@1979: * Returns an instance of EmptySettings currently! ingo@1979: * ingo@1979: * @return an instance of EmptySettings. ingo@1979: */ ingo@1979: public Settings getSettings() { ingo@1979: return new EmptySettings(); ingo@1979: } ingo@2047: ingo@2047: ingo@2047: /** ingo@2047: * This method is not implemented. Override it in subclasses if those need a ingo@2047: * Settings object. ingo@2047: */ ingo@2047: public void setSettings(Settings settings) { ingo@2047: // do nothing ingo@2047: } felix@2284: felix@2284: felix@2284: /** felix@2284: * Returns the number formatter for kilometer values. felix@2284: * felix@2284: * @return the number formatter for kilometer values. felix@2284: */ felix@2284: protected NumberFormat getKmFormatter() { felix@5111: if (kmFormat == null) { felix@5111: kmFormat = Formatter.getWaterlevelKM(context); felix@5111: } felix@5111: return kmFormat; felix@2284: } felix@2284: felix@2284: felix@2284: /** felix@2284: * Returns the number formatter for W values. felix@2284: * felix@2284: * @return the number formatter for W values. felix@2284: */ felix@2284: protected NumberFormat getWFormatter() { felix@5111: if (wFormat == null) { felix@5111: wFormat = Formatter.getWaterlevelW(context); felix@5111: } felix@5111: return wFormat; felix@2284: } felix@2284: felix@2284: felix@2284: /** felix@2284: * Returns the number formatter for Q values. felix@2284: * felix@2284: * @return the number formatter for Q values. felix@2284: */ felix@2284: protected NumberFormat getQFormatter() { felix@5111: if (qFormat == null) { felix@5111: qFormat = Formatter.getWaterlevelQ(context); felix@5111: } felix@5111: return qFormat; felix@2284: } gernotbelger@8862: gernotbelger@8862: protected final NumberFormat getMeanBedHeighFormatter() { gernotbelger@8862: if( meanBedHeightFormat == null ) gernotbelger@8862: meanBedHeightFormat = Formatter.getMeanBedHeight(context); gernotbelger@8862: return meanBedHeightFormat; gernotbelger@8862: } gernotbelger@8862: gernotbelger@8862: protected final NumberFormat getTkhFormatter() { gernotbelger@8862: if( tkhFormat == null ) gernotbelger@8862: tkhFormat = Formatter.getTkh(context); gernotbelger@8862: return tkhFormat; gernotbelger@8862: } gernotbelger@8862: gernotbelger@8862: protected final NumberFormat getFlowDepthFormatter() { gernotbelger@8862: if( flowDepthFormat == null ) gernotbelger@8862: flowDepthFormat = Formatter.getFlowDepth(context); gernotbelger@8862: return flowDepthFormat; gernotbelger@8862: } gernotbelger@8862: gernotbelger@8862: protected final NumberFormat getW2Formatter() { gernotbelger@8862: if( w2Format == null ) gernotbelger@8862: w2Format = Formatter.getFlowDepth(context); gernotbelger@8862: return w2Format; gernotbelger@8862: } ingo@391: } ingo@391: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :