ingo@0: /*
ingo@0:  * Copyright (c) 2010 by Intevation GmbH
ingo@0:  *
ingo@0:  * This program is free software under the LGPL (>=v2.1)
ingo@0:  * Read the file LGPL.txt coming with the software for details
ingo@0:  * or visit http://www.gnu.org/licenses/ if it does not exist.
ingo@0:  */
ingo@0: 
ingo@0: package de.intevation.artifacts.httpclient;
ingo@0: 
ingo@0: import java.io.IOException;
ingo@0: import java.io.File;
ingo@0: import java.io.FileOutputStream;
ingo@0: import java.io.OutputStream;
ingo@0: 
ingo@0: import java.net.MalformedURLException;
ingo@0: 
ingo@0: import java.util.Arrays;
ingo@0: import java.util.ArrayList;
ingo@0: import java.util.HashMap;
ingo@0: import java.util.List;
ingo@0: import java.util.Map;
ingo@0: 
ingo@0: import javax.xml.xpath.XPathConstants;
ingo@0: 
ingo@0: import org.w3c.dom.Document;
ingo@0: import org.w3c.dom.Node;
ingo@0: import org.w3c.dom.NodeList;
ingo@0: 
ingo@0: import org.apache.log4j.Logger;
ingo@0: import org.apache.log4j.PropertyConfigurator;
ingo@0: 
ingo@1: import de.intevation.artifacts.httpclient.http.HttpClient;
ingo@1: import de.intevation.artifacts.httpclient.http.HttpClientImpl;
ingo@0: import de.intevation.artifacts.httpclient.http.response.DocumentResponseHandler;
ingo@0: 
ingo@0: import de.intevation.artifacts.httpclient.exceptions.ConnectionException;
ingo@0: import de.intevation.artifacts.httpclient.exceptions.NoSuchOptionException;
ingo@0: import de.intevation.artifacts.httpclient.objects.Artifact;
ingo@0: import de.intevation.artifacts.httpclient.utils.ArtifactProtocolUtils;
ingo@0: import de.intevation.artifacts.httpclient.utils.Configuration;
ingo@0: import de.intevation.artifacts.httpclient.utils.XFormNamespaceContext;
ingo@0: import de.intevation.artifacts.httpclient.utils.XMLUtils;
ingo@0: 
ingo@0: /**
ingo@0:  * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
ingo@0:  */
ingo@0: public class ConsoleClient
ingo@0: {
ingo@0:     /**
ingo@0:      * The logging is done via Log4j. To configure the logging
ingo@0:      * a file 'log4j.properties' is search in the configuration directory.
ingo@0:      */
ingo@0:     public static final String LOG4J_PROPERTIES = "log4j.properties";
ingo@0: 
ingo@0: 
ingo@0:     /**
ingo@0:      * The path of the configuration directory.
ingo@0:      */
ingo@0:     public static final String CONFIG_PATH = System.getProperty("config.dir",
ingo@0:                                                                 "conf");
ingo@0: 
ingo@0:     public static final String CONFIG = System.getProperty("config.file",
ingo@0:                                                            "use_case1.conf");
ingo@0: 
ingo@0: 
ingo@0:     public static final String XPATH_DYNAMIC = "/art:result/art:ui/art:dynamic";
ingo@0: 
ingo@0:     /**
ingo@0:      * The logger used in this class.
ingo@0:      */
ingo@0:     private static Logger logger;
ingo@0: 
ingo@0: 
ingo@0:     static {
ingo@0:         configureLogging();
ingo@0: 
ingo@0:         logger = Logger.getLogger(ConsoleClient.class);
ingo@0:     }
ingo@0: 
ingo@0: 
ingo@0:     /**
ingo@0:      * Trys to load the Log4j configuration from ${config.dir}/log4j.properties.
ingo@0:      */
ingo@0:     public static final void configureLogging() {
ingo@0:         File configDir = new File(CONFIG_PATH);
ingo@0:         File propFile  = new File(configDir, LOG4J_PROPERTIES);
ingo@0: 
ingo@0:         if (propFile.isFile() && propFile.canRead()) {
ingo@0:             try {
ingo@0:                 PropertyConfigurator.configure(propFile.toURI().toURL());
ingo@0:             }
ingo@0:             catch (MalformedURLException mue) {
ingo@0:                 mue.printStackTrace(System.err);
ingo@0:             }
ingo@0:         }
ingo@0:     }
ingo@0: 
ingo@0: 
ingo@0:     public static final Configuration readConfiguration() {
ingo@0:         File configDir  = new File(CONFIG_PATH);
ingo@0:         File configFile = new File(configDir, CONFIG);
ingo@0: 
ingo@0:         logger.debug("Configuration file: " + configFile.getAbsolutePath());
ingo@0: 
ingo@0:         if (configFile.isFile() && configFile.canRead()) {
ingo@0:             try {
ingo@0:                 Configuration conf = new Configuration(configFile);
ingo@0:                 conf.initialize();
ingo@0: 
ingo@0:                 return conf;
ingo@0:             }
ingo@0:             catch (IOException ioe) {
ingo@0:                 logger.error("Error while reading configuration.");
ingo@0:             }
ingo@0:         }
ingo@0: 
ingo@0:         return null;
ingo@0:     }
ingo@0: 
ingo@0: 
ingo@0:     public static void main( String[] args )
ingo@0:     {
ingo@1:         logger.info("Starting console client.");
ingo@0: 
ingo@0:         Configuration conf = readConfiguration();
ingo@0: 
ingo@0:         String serverHost  = (String) conf.getServerSettings("host");
ingo@0:         String serverPort  = (String) conf.getServerSettings("port");
ingo@1:         HttpClient client   = new HttpClientImpl(serverHost + ":" + serverPort);
ingo@0: 
ingo@0:         try {
ingo@0:             Document create = ArtifactProtocolUtils.createCreateDocument(
ingo@0:                 (String) conf.getArtifactSettings("fis"));
ingo@2:             Artifact artifact = (Artifact) client.create(create, null);
ingo@0: 
ingo@0:             Map attr       = new HashMap();
ingo@0:             String   product  = (String) conf.getArtifactSettings("product");
ingo@0:             String[] products = extractOptions(client, artifact, product);
ingo@0:             attr.put("product", products[0]);
ingo@0: 
ingo@0:             feedAndGo(client, artifact, attr, "timeSeries");
ingo@0: 
ingo@0:             attr.clear();
ingo@0:             String   area  = (String) conf.getArtifactSettings("areaid");
ingo@0:             String[] areas = extractOptions(client, artifact, area);
ingo@0:             attr.put("areaid", areas[0]);
ingo@0:             feedAndGo(client, artifact, attr, "timeseries_without_geom");
ingo@0: 
ingo@0:             attr.clear();
ingo@0:             String   feature  = (String) conf.getArtifactSettings("featureid");
ingo@0:             String[] features = extractOptions(client, artifact, feature);
ingo@0:             attr.put("featureid", features[0]);
ingo@0:             feedAndGo(client, artifact, attr, "timeseries_vector_scalar");
ingo@0: 
ingo@0:             attr.clear();
ingo@0:             String   vector  = (String) conf.getArtifactSettings("vectorscalar");
ingo@0:             String[] vectors = extractOptions(client, artifact, vector);
ingo@0:             attr.put("vectorscalar", vectors[0]);
ingo@0:             feedAndGo(client, artifact, attr, "timeseries_parameter");
ingo@0: 
ingo@0:             attr.clear();
ingo@0:             String   parameter  = (String) conf.getArtifactSettings("parameterid");
ingo@0:             String[] parameters = extractOptions(client, artifact, parameter);
ingo@0:             attr.put("parameterid", parameters);
ingo@0:             feedAndGo(client, artifact, attr, "timeseries_depth_height");
ingo@0: 
ingo@0:             attr.clear();
ingo@0:             String   measure  = (String) conf.getArtifactSettings("measurementid");
ingo@0:             String[] measures = extractMeasurements(client, artifact, measure);
ingo@0:             attr.put("measurementid", measures);
ingo@0:             feedAndGo(client, artifact, attr, "timeseries_interval");
ingo@0: 
ingo@0:             attr.clear();
ingo@0:             String min = (String) conf.getArtifactSettings("minvalue");
ingo@0:             String max = (String) conf.getArtifactSettings("maxvalue");
ingo@0:             attr.put("minvalue", min);
ingo@0:             attr.put("maxvalue", max);
ingo@0:             feedAndGo(client, artifact, attr, "timeseries_calculate_results");
ingo@0: 
ingo@0:             try {
ingo@0:                 Map opts = new HashMap();
ingo@0:                 opts.put("mime-type", conf.getOutputSettings("mime-type"));
ingo@0:                 opts.put("width", conf.getOutputSettings("width"));
ingo@0:                 opts.put("height", conf.getOutputSettings("height"));
ingo@0:                 opts.put("points", conf.getOutputSettings("points"));
ingo@0: 
ingo@0:                 Document chart =
ingo@0:                     ArtifactProtocolUtils.createChartDocument(artifact, opts);
ingo@0: 
ingo@0:                 String dir = (String) conf.getOutputSettings("directory");
ingo@0: 
ingo@0:                 File outDir     = new File(dir);
ingo@0:                 File output     = new File(outDir, "output.png");
ingo@0:                 OutputStream os = new FileOutputStream(output);
ingo@0: 
ingo@0:                 client.out(artifact, chart, "chart", os);
ingo@0:             }
ingo@0:             catch (IOException ioe) {
ingo@0:                 logger.error(
ingo@0:                     "IO error while writing the output: " + ioe.getMessage());
ingo@0:             }
ingo@0: 
ingo@1:             logger.debug("Finished console client.");
ingo@0:         }
ingo@0:         catch (ConnectionException ce) {
ingo@0:             logger.error(ce.getMessage());
ingo@0:         }
ingo@0:         catch (NoSuchOptionException nsoe) {
ingo@0:             logger.error(
ingo@0:                 "No such option found: " + nsoe.getMessage());
ingo@0:         }
ingo@0:     }
ingo@0: 
ingo@0: 
ingo@0:     public static void feedAndGo(
ingo@1:         HttpClient client,
ingo@0:         Artifact  artifact,
ingo@0:         Map       attr,
ingo@0:         String target)
ingo@0:     throws ConnectionException
ingo@0:     {
ingo@0:         Document feed = ArtifactProtocolUtils.createFeedDocument(artifact, attr);
ingo@0:         client.feed(artifact, feed, new DocumentResponseHandler());
ingo@0: 
ingo@0:         Document advance = ArtifactProtocolUtils.createAdvanceDocument(
ingo@0:             artifact,
ingo@0:             target);
ingo@0: 
ingo@0:         client.advance(artifact, advance, new DocumentResponseHandler());
ingo@0:     }
ingo@0: 
ingo@0: 
ingo@0:     /**
ingo@0:      * XXX I think, this method should be implemented somewhere else to be able
ingo@0:      * to re-use this implementation. But this method needs more work to be more
ingo@0:      * abstract, so it needs to be reimplemented later, I think.
ingo@0:      */
ingo@0:     public static String[] extractOptions(
ingo@1:         HttpClient client,
ingo@0:         Artifact  artifact,
ingo@0:         String    text)
ingo@0:     throws NoSuchOptionException, ConnectionException
ingo@0:     {
ingo@0:         Document describe = ArtifactProtocolUtils.createDescribeDocument(
ingo@0:             artifact, true);
ingo@0: 
ingo@0:         Document description = (Document) client.describe(
ingo@0:             artifact, describe, new DocumentResponseHandler());
ingo@0: 
ingo@0:         List pieces  = Arrays.asList(text.split(","));
ingo@0:         List options = new ArrayList(pieces.size()); 
ingo@0: 
ingo@0:         Node dynamic   = XMLUtils.getNodeXPath(description, XPATH_DYNAMIC);
ingo@0: 
ingo@0:         // TODO We should handle these cases better!!
ingo@0:         NodeList items = (NodeList) XMLUtils.getXPath(
ingo@0:             dynamic, "xform:select1/xform:choices/xform:item",
ingo@0:             XPathConstants.NODESET, XFormNamespaceContext.INSTANCE);
ingo@0: 
ingo@0:         if (items == null || items.getLength() == 0) {
ingo@0:             items = (NodeList) XMLUtils.getXPath(
ingo@0:                 dynamic, "xform:select/xform:choices/xform:item",
ingo@0:                 XPathConstants.NODESET, XFormNamespaceContext.INSTANCE);
ingo@0:         }
ingo@0: 
ingo@0:         if (items == null || items.getLength() == 0) {
ingo@0:             items = (NodeList) XMLUtils.getXPath(
ingo@0:                 dynamic, "xform:group/xform:select/xform:item",
ingo@0:                 XPathConstants.NODESET, XFormNamespaceContext.INSTANCE);
ingo@0:         }
ingo@0: 
ingo@0: 
ingo@0:         for (int i = 0; i < items.getLength(); i++) {
ingo@0:             Node item  = items.item(i);
ingo@0:             Node label = (Node) XMLUtils.getXPath(
ingo@0:                 item, "xform:label", XPathConstants.NODE,
ingo@0:                 XFormNamespaceContext.INSTANCE);
ingo@0: 
ingo@0:             Node value = (Node) XMLUtils.getXPath(
ingo@0:                 item, "xform:value", XPathConstants.NODE,
ingo@0:                 XFormNamespaceContext.INSTANCE);
ingo@0: 
ingo@0:             if (pieces.indexOf(label.getTextContent()) >= 0)
ingo@0:                 options.add(value.getTextContent());
ingo@0:         }
ingo@0: 
ingo@0:         if (options.isEmpty())
ingo@0:             throw new NoSuchOptionException(text);
ingo@0: 
ingo@0:         return (String[]) options.toArray(new String[options.size()]);
ingo@0:     }
ingo@0: 
ingo@0: 
ingo@0:     /**
ingo@0:      * XXX This method extracts the measurement ids depending on the user
ingo@0:      * configuration from describe document. Currently, this is a special case
ingo@0:      * that should be handled the same way as all the other options in the
ingo@0:      * describe document.
ingo@0:      */
ingo@0:     public static String[] extractMeasurements(
ingo@1:         HttpClient client,
ingo@0:         Artifact  artifact,
ingo@0:         String    text)
ingo@0:     throws NoSuchOptionException, ConnectionException
ingo@0:     {
ingo@0:         Document describe = ArtifactProtocolUtils.createDescribeDocument(
ingo@0:             artifact, true);
ingo@0: 
ingo@0:         Document description = (Document) client.describe(
ingo@0:             artifact, describe, new DocumentResponseHandler());
ingo@0: 
ingo@0:         List pieces  = Arrays.asList(text.split(","));
ingo@0:         List options = new ArrayList(pieces.size());
ingo@0: 
ingo@0:         Node dynamic = XMLUtils.getNodeXPath(description, XPATH_DYNAMIC);
ingo@0: 
ingo@0:         NodeList params = (NodeList) XMLUtils.getXPath(
ingo@0:             dynamic, "xform:group/xform:select",
ingo@0:             XPathConstants.NODESET, XFormNamespaceContext.INSTANCE);
ingo@0: 
ingo@0:         for (int i = 0; i < params.getLength(); i++) {
ingo@0:             Node param = params.item(i);
ingo@0: 
ingo@0:             NodeList items = (NodeList) XMLUtils.getXPath(
ingo@0:                 param, "xform:item[@disabled='false']", XPathConstants.NODESET,
ingo@0:                 XFormNamespaceContext.INSTANCE);
ingo@0: 
ingo@0:             for (int j = 0; j < items.getLength(); j++) {
ingo@0:                 Node item = items.item(j);
ingo@0: 
ingo@0:                 Node label = (Node) XMLUtils.getXPath(
ingo@0:                     item, "xform:label", XPathConstants.NODE,
ingo@0:                     XFormNamespaceContext.INSTANCE);
ingo@0: 
ingo@0:                 if (pieces.indexOf(label.getTextContent()) < 0) {
ingo@0:                     continue;
ingo@0:                 }
ingo@0: 
ingo@0:                 Node value = (Node) XMLUtils.getXPath(
ingo@0:                     item, "xform:value", XPathConstants.NODE,
ingo@0:                     XFormNamespaceContext.INSTANCE);
ingo@0: 
ingo@0:                 options.add(value.getTextContent());
ingo@0:             }
ingo@0:         }
ingo@0: 
ingo@0:         if (options.isEmpty())
ingo@0:             throw new NoSuchOptionException(text);
ingo@0: 
ingo@0:         return (String[]) options.toArray(new String[options.size()]);
ingo@0:     }
ingo@0: }
ingo@0: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8: