teichmann@5835: package org.dive4elements.river.client.server; ingo@16: ingo@16: import java.util.ArrayList; ingo@16: import java.util.List; ingo@16: ingo@16: import javax.xml.xpath.XPathConstants; ingo@16: ingo@16: import org.w3c.dom.Document; ingo@803: import org.w3c.dom.Element; ingo@16: import org.w3c.dom.Node; ingo@16: import org.w3c.dom.NodeList; ingo@16: ingo@1367: import org.apache.log4j.Logger; ingo@1367: teichmann@5835: import org.dive4elements.artifacts.common.ArtifactNamespaceContext; teichmann@5835: import org.dive4elements.artifacts.common.utils.ClientProtocolUtils; teichmann@5835: import org.dive4elements.artifacts.common.utils.XMLUtils; ingo@16: teichmann@5835: import org.dive4elements.river.client.shared.model.ArtifactDescription; teichmann@5835: import org.dive4elements.river.client.shared.model.DataItem; teichmann@5835: import org.dive4elements.river.client.shared.model.DataList; teichmann@5835: import org.dive4elements.river.client.shared.model.DefaultArtifactDescription; teichmann@5835: import org.dive4elements.river.client.shared.model.DefaultData; teichmann@5835: import org.dive4elements.river.client.shared.model.DefaultDataItem; teichmann@5835: import org.dive4elements.river.client.shared.model.DefaultOutputMode; teichmann@5835: import org.dive4elements.river.client.shared.model.DoubleArrayData; teichmann@5835: import org.dive4elements.river.client.shared.model.DoubleRangeData; teichmann@5835: import org.dive4elements.river.client.shared.model.IntegerArrayData; teichmann@5835: import org.dive4elements.river.client.shared.model.IntegerRangeData; teichmann@5835: import org.dive4elements.river.client.shared.model.IntegerOptionsData; teichmann@5835: import org.dive4elements.river.client.shared.model.LongRangeData; teichmann@5835: import org.dive4elements.river.client.shared.model.OutputMode; teichmann@5835: import org.dive4elements.river.client.shared.model.Recommendation; teichmann@5835: import org.dive4elements.river.client.shared.model.WQDataItem; ingo@16: ingo@16: ingo@16: /** ingo@16: * This factory class helps creating an {@link ArtifactDescription} based on the ingo@16: * DESCRIBE document of an artifact returned by the artifact server. Use the ingo@16: * {@link createArtifactDescription(org.w3c.dom.Document)} method with the ingo@16: * DESCRIBE document to create such an {@link ArtifactDescription}. ingo@16: * ingo@16: * @author Ingo Weinzierl ingo@16: */ ingo@16: public class ArtifactDescriptionFactory { ingo@16: ingo@1367: private static final Logger logger = ingo@1367: Logger.getLogger(ArtifactDescriptionFactory.class); ingo@1367: ingo@1367: ingo@16: public static final String XPATH_STATE_NAME = "@art:name"; ingo@16: ingo@22: public static final String XPATH_UIPROVIDER = "@art:uiprovider"; ingo@22: ingo@2500: public static final String XPATH_HELP_TEXT = "@art:helpText"; ingo@2500: ingo@32: public static final String XPATH_REACHABLE_STATE = "art:state"; ingo@32: ingo@51: public static final String XPATH_STATIC_STATE_NODE = "art:state"; ingo@51: ingo@34: public static final String XPATH_STATIC_DATA_NODE = "art:data"; ingo@34: ingo@34: public static final String XPATH_STATIC_ITEM_NODE = "art:item"; ingo@34: ingo@803: public static final String XPATH_RECOMMENDED_ARTIFACTS = ingo@808: "/art:result/art:recommended-artifacts//*[@factory]"; ingo@803: ingo@16: /** ingo@16: * This method creates the {@link ArtifactDescription} of the DESCRIBE ingo@16: * document doc. ingo@16: * ingo@16: * @param doc A DESCRIBE document. ingo@16: * ingo@16: * @return the {@link ArtifactDescription}. ingo@16: */ ingo@16: public static ArtifactDescription createArtifactDescription(Document doc) { ingo@1367: logger.debug("ArtifactDescriptionFactory.createArtifactDescription"); ingo@16: ingo@16: Node currentState = ClientProtocolUtils.getCurrentState(doc); ingo@16: Node staticNode = ClientProtocolUtils.getStaticUI(doc); ingo@16: Node dynamicNode = ClientProtocolUtils.getDynamicUI(doc); ingo@32: Node reachable = ClientProtocolUtils.getReachableStates(doc); ingo@65: NodeList outputs = ClientProtocolUtils.getOutputModes(doc); ingo@16: ingo@16: String state = (String) XMLUtils.xpath( ingo@16: currentState, ingo@16: XPATH_STATE_NAME, ingo@16: XPathConstants.STRING, ingo@16: ArtifactNamespaceContext.INSTANCE); ingo@1367: ingo@1367: logger.debug("Current state name: " + state); ingo@16: ingo@51: DataList currentData = extractCurrentData(dynamicNode, state); ingo@51: DataList[] old = extractOldData(staticNode); ingo@34: String[] states = extractReachableStates(reachable); ingo@803: OutputMode[] outs = extractOutputModes(outputs); ingo@807: Recommendation[] rec = extractRecommendedArtifacts(doc); ingo@16: ingo@65: return new DefaultArtifactDescription( ingo@65: old, ingo@65: currentData, ingo@65: state, ingo@65: states, ingo@803: outs, ingo@807: rec); ingo@16: } ingo@16: ingo@16: ingo@16: /** ingo@16: * This method extracts the data that the user is able to enter in the ingo@16: * current state of the artifact. ingo@16: * ingo@16: * @param dynamicNode The dynamic node of the DESCRIBE document. ingo@51: * @param state The name of the current state. ingo@16: * ingo@16: * @return A {@link Data} object that represents the data which might be ingo@16: * entered by the user in the current state or null, if no data might be ingo@16: * entered. ingo@16: */ ingo@51: protected static DataList extractCurrentData(Node dynamicNode, String state) { ingo@1367: logger.debug("ArtifactDescriptionFactory.extractCurrentData"); ingo@16: ingo@51: NodeList data = ClientProtocolUtils.getSelectNode(dynamicNode); ingo@2500: String help = extractHelpText(dynamicNode); ingo@51: String uiProvider = extractUIProvider(dynamicNode); ingo@16: ingo@51: if (data == null || data.getLength() == 0) { ingo@51: return null; ingo@51: } ingo@16: ingo@1522: int dataNum = data.getLength(); ingo@2500: DataList list = new DataList(state, dataNum, uiProvider, null, help); ingo@51: ingo@51: for (int i = 0; i < dataNum; i++) { ingo@1522: Element d = (Element) data.item(i); ingo@1522: String label = ClientProtocolUtils.getLabel(d); ingo@1522: String name = XMLUtils.xpathString( ingo@1522: d, "@art:name", ArtifactNamespaceContext.INSTANCE); ingo@1522: String type = XMLUtils.xpathString( ingo@1522: d, "@art:type", ArtifactNamespaceContext.INSTANCE); ingo@51: ingo@1522: logger.debug("Create new IntegerRangeData object for: " + name); ingo@1522: logger.debug("New Data is from type: " + type); ingo@51: felix@1592: // TODO replace with DataFactory. felix@1592: ingo@1522: if (type == null || type.length() == 0) { ingo@1522: NodeList choices = ClientProtocolUtils.getItemNodes(d); ingo@1522: DataItem[] dataItems = extractCurrentDataItems(choices); ingo@1522: DataItem def = extractDefaultDataItem(d); ingo@51: ingo@1522: list.add(new DefaultData(name, label, null, dataItems, def)); ingo@1522: } ingo@1522: else if (type.equals("intrange")) { ingo@1566: String min = ClientProtocolUtils.getMinNode(d); ingo@1566: String max = ClientProtocolUtils.getMaxNode(d); ingo@1566: ingo@1566: String defMin = ClientProtocolUtils.getDefMin(d); ingo@1566: String defMax = ClientProtocolUtils.getDefMax(d); ingo@1522: ingo@1522: try { ingo@1522: int lower = Integer.parseInt(min); ingo@1522: int upper = Integer.parseInt(max); ingo@1566: ingo@1566: if (defMin != null && defMax != null) { ingo@1566: list.add(new IntegerRangeData( ingo@1566: name, label, ingo@1566: lower, upper, ingo@1566: Integer.parseInt(defMin), ingo@1566: Integer.parseInt(defMax))); ingo@1566: } ingo@1566: else { ingo@1566: list.add( ingo@1566: new IntegerRangeData(name, label, lower, upper)); ingo@1566: } ingo@1522: } ingo@1522: catch (NumberFormatException nfe) { ingo@1522: logger.warn("NumberFormatException: ", nfe); ingo@1522: } ingo@1522: } ingo@4132: else if (type.equals("longrange")) { ingo@4132: String min = ClientProtocolUtils.getMinNode(d); ingo@4132: String max = ClientProtocolUtils.getMaxNode(d); ingo@4132: ingo@4132: String defMin = ClientProtocolUtils.getDefMin(d); ingo@4132: String defMax = ClientProtocolUtils.getDefMax(d); ingo@4132: ingo@4132: try { ingo@4132: long lower = Long.valueOf(min); ingo@4132: long upper = Long.valueOf(max); ingo@4132: ingo@4132: if (defMin != null && defMax != null) { ingo@4132: list.add(new LongRangeData( ingo@4132: name, label, ingo@4132: lower, upper, ingo@4132: Long.valueOf(defMin), ingo@4132: Long.valueOf(defMax))); ingo@4132: } ingo@4132: } ingo@4132: catch (NumberFormatException nfe) { ingo@4132: logger.warn("NumberFormatException: ", nfe); ingo@4132: } ingo@4132: } ingo@1527: else if (type.equals("intarray")) { ingo@1527: list.add(new IntegerArrayData(name, label, null)); ingo@1527: } ingo@2521: else if (type.equals("intoptions") && uiProvider.equals("parameter-matrix")) { ingo@2532: list.add(DataFactory.createIntegerOptionsData(d, name, label)); ingo@2532: } ingo@2532: else if (type.equals("options")) { ingo@2532: list.add(DataFactory.createStringOptionsData(d, name, label)); ingo@2521: } ingo@1527: else if (type.equals("intoptions")) { ingo@1527: NodeList choices = ClientProtocolUtils.getItemNodes(d); ingo@1527: DataItem[] opts = extractCurrentDataItems(choices); ingo@1527: ingo@1527: list.add(new IntegerOptionsData(name, label, opts)); ingo@1527: } ingo@1595: else if (type.equals("doublearray")) { ingo@1595: list.add(new DoubleArrayData(name, label, null)); ingo@1595: } felix@1592: else { felix@1592: logger.warn("Unrecognized Dynamic data type."); felix@1592: NodeList choices = ClientProtocolUtils.getItemNodes(d); felix@1592: DataItem[] dataItems = extractCurrentDataItems(choices); felix@1592: DataItem def = extractDefaultDataItem(d); felix@1592: felix@1593: String min = ClientProtocolUtils.getMinNode(d); felix@1593: String max = ClientProtocolUtils.getMaxNode(d); felix@1593: if (min != null && max != null) { felix@1593: list.add(new DoubleRangeData( felix@1593: name, label, felix@1593: Double.valueOf(min), Double.valueOf(max), felix@1593: Double.valueOf(min), Double.valueOf(max))); felix@1593: } felix@1593: felix@1592: list.add(new DefaultData(name, label, null, dataItems, def)); felix@1592: } felix@1592: ingo@51: } ingo@51: ingo@51: return list; ingo@16: } ingo@16: ingo@16: ingo@16: /** ingo@515: * This method extracts the default value of a Data object. ingo@515: * ingo@515: * @param data The data object node. ingo@515: * ingo@515: * @return the default DataItem. ingo@515: */ ingo@515: protected static DataItem extractDefaultDataItem(Node data) { ingo@1367: logger.debug("ArtifactDescriptionFactory.extractDefaultDataItem"); ingo@515: ingo@515: String value = XMLUtils.xpathString( ingo@515: data, "@art:defaultValue", ArtifactNamespaceContext.INSTANCE); ingo@515: ingo@515: String label = XMLUtils.xpathString( ingo@515: data, "@art:defaultLabel", ArtifactNamespaceContext.INSTANCE); ingo@515: ingo@515: if (value != null && label != null) { ingo@515: return new DefaultDataItem(label, null, value); ingo@515: } ingo@515: ingo@515: return null; ingo@515: } ingo@515: ingo@515: ingo@515: /** ingo@16: * This method extract the {@link DataItem}s of the DESCRIBE document. ingo@16: * ingo@16: * @param items The items in the DESCRIBE document. ingo@16: * ingo@16: * @return the {@link DataItem}s. ingo@16: */ ingo@16: protected static DataItem[] extractCurrentDataItems(NodeList items) { ingo@1367: logger.debug("ArtifactDescriptionFactory.extractCurrentDataItems"); ingo@16: ingo@16: if (items == null || items.getLength() == 0) { ingo@1367: logger.debug("No data items found."); ingo@16: return null; ingo@16: } ingo@16: ingo@16: int count = items.getLength(); ingo@16: ingo@16: List dataItems = new ArrayList(count); ingo@16: ingo@16: for (int i = 0; i < count; i++) { ingo@16: Node item = items.item(i); ingo@16: String label = ClientProtocolUtils.getLabel(item); ingo@16: String value = ClientProtocolUtils.getValue(item); ingo@16: ingo@565: double[] mmQ = extractMinMaxQValues(item); ingo@565: double[] mmW = extractMinMaxWValues(item); ingo@565: ingo@565: if (mmQ != null || mmW != null) { ingo@565: dataItems.add(new WQDataItem(label, null, value, mmQ, mmW)); ingo@565: } ingo@565: else { ingo@565: dataItems.add(new DefaultDataItem(label, null, value)); ingo@565: } ingo@16: } ingo@16: sascha@3379: return dataItems.toArray(new DataItem[count]); ingo@16: } ingo@22: ingo@22: ingo@565: protected static double[] extractMinMaxQValues(Node item) { ingo@1367: logger.debug("ArtifactDescriptionFactory.extractMinMaxQValues"); ingo@565: ingo@565: if (item == null) { ingo@1367: logger.debug("This node is empty - no min/max Q values."); ingo@565: return null; ingo@565: } ingo@565: ingo@565: Node node = (Node) XMLUtils.xpath( ingo@565: item, ingo@565: "art:range[@art:type='Q']", ingo@565: XPathConstants.NODE, ingo@565: ArtifactNamespaceContext.INSTANCE); ingo@565: ingo@565: if (node == null) { ingo@1367: logger.debug("No min/max Q values found."); ingo@565: return null; ingo@565: } ingo@565: ingo@565: return extractMinMaxValues(node); ingo@565: } ingo@565: ingo@565: ingo@565: protected static double[] extractMinMaxWValues(Node item) { ingo@1367: logger.debug("ArtifactDescriptionFactory.extractMinMaxWValues"); ingo@565: ingo@565: if (item == null) { ingo@1367: logger.debug("This node is empty - no min/max W values."); ingo@565: return null; ingo@565: } ingo@565: ingo@565: Node node = (Node) XMLUtils.xpath( ingo@565: item, ingo@565: "art:range[@art:type='W']", ingo@565: XPathConstants.NODE, ingo@565: ArtifactNamespaceContext.INSTANCE); ingo@565: ingo@565: if (node == null) { ingo@1367: logger.debug("No min/max W values found."); ingo@565: return null; ingo@565: } ingo@565: ingo@565: return extractMinMaxValues(node); ingo@565: } ingo@565: ingo@565: ingo@565: protected static double[] extractMinMaxValues(Node node) { ingo@1367: logger.debug("ArtifactDescriptionFactory.extractMinMaxValues"); ingo@565: ingo@565: String minStr = XMLUtils.xpathString( ingo@565: node, "art:min/text()", ArtifactNamespaceContext.INSTANCE); ingo@565: ingo@565: String maxStr = XMLUtils.xpathString( ingo@565: node, "art:max/text()", ArtifactNamespaceContext.INSTANCE); ingo@565: ingo@565: if (maxStr == null || minStr == null) { ingo@1367: logger.debug("No min/max values found."); ingo@565: return null; ingo@565: } ingo@565: ingo@565: try { ingo@565: double min = Double.valueOf(minStr); ingo@565: double max = Double.valueOf(maxStr); ingo@565: ingo@565: return new double[] { min, max }; ingo@565: } ingo@565: catch (NumberFormatException nfe) { ingo@1367: logger.debug("Error while parsing min/max values."); ingo@565: } ingo@565: ingo@565: return null; ingo@565: } ingo@565: ingo@565: ingo@22: /** ingo@34: * This method extracts the data objects from the data node of the static ui ingo@34: * part of the DESCRIBE document. ingo@34: * ingo@34: * @param staticNode The static ui node of the DESCRIBE. ingo@34: * ingo@51: * @return the DataList objects. ingo@34: */ ingo@51: protected static DataList[] extractOldData(Node staticNode) { ingo@1367: logger.debug("ArtifactDescriptionFactory.extractOldData()"); ingo@34: ingo@51: NodeList stateNodes = (NodeList) XMLUtils.xpath( ingo@34: staticNode, ingo@51: XPATH_STATIC_STATE_NODE, ingo@34: XPathConstants.NODESET, ingo@34: ArtifactNamespaceContext.INSTANCE); ingo@34: ingo@51: if (stateNodes == null || stateNodes.getLength() == 0) { ingo@1367: logger.debug("No old items found."); ingo@34: return null; ingo@34: } ingo@34: ingo@51: int count = stateNodes.getLength(); ingo@51: DataList[] data = new DataList[count]; ingo@34: ingo@34: for (int i = 0; i < count; i++) { ingo@51: Node tmp = stateNodes.item(i); ingo@34: ingo@34: String name = XMLUtils.xpathString( ingo@34: tmp, "@art:name", ArtifactNamespaceContext.INSTANCE); ingo@52: String uiprovider = XMLUtils.xpathString( ingo@52: tmp, "@art:uiprovider", ArtifactNamespaceContext.INSTANCE); ingo@52: String label = XMLUtils.xpathString( ingo@52: tmp, "@art:label", ArtifactNamespaceContext.INSTANCE); ingo@2500: String help = XMLUtils.xpathString( ingo@2500: tmp, "@art:helpText", ArtifactNamespaceContext.INSTANCE); ingo@34: ingo@51: NodeList dataNodes = (NodeList) XMLUtils.xpath( ingo@51: tmp, ingo@51: XPATH_STATIC_DATA_NODE, ingo@51: XPathConstants.NODESET, ingo@51: ArtifactNamespaceContext.INSTANCE); ingo@34: ingo@51: if (dataNodes == null || dataNodes.getLength() == 0) { ingo@51: continue; ingo@51: } ingo@51: ingo@51: int size = dataNodes.getLength(); ingo@2500: DataList list = new DataList(name, size, uiprovider, label, help); ingo@51: ingo@51: for (int j = 0; j < size; j++) { ingo@51: Node dataNode = dataNodes.item(j); ingo@51: ingo@1571: list.add(DataFactory.createDataFromElement((Element) dataNode)); ingo@51: ingo@51: data[i] = list; ingo@51: } ingo@34: } ingo@34: ingo@34: return data; ingo@34: } ingo@34: ingo@34: ingo@34: /** ingo@22: * This method extracts the UIProvider specified by the data node. ingo@22: * ingo@22: * @param data The data node. ingo@22: * ingo@22: * @return the UIProvider that is specified in the data node. ingo@22: */ ingo@51: protected static String extractUIProvider(Node ui) { ingo@22: return (String) XMLUtils.xpath( ingo@51: ui, ingo@22: XPATH_UIPROVIDER, ingo@22: XPathConstants.STRING, ingo@22: ArtifactNamespaceContext.INSTANCE); ingo@22: } ingo@32: ingo@32: ingo@32: /** ingo@2500: * This method extracts the help text specified by the data node. ingo@2500: * ingo@2500: * @param ui The data node. ingo@2500: * ingo@2500: * @return the help text. ingo@2500: */ ingo@2500: protected static String extractHelpText(Node ui) { ingo@2500: return (String) XMLUtils.xpath( ingo@2500: ui, ingo@2500: XPATH_HELP_TEXT, ingo@2500: XPathConstants.STRING, ingo@2500: ArtifactNamespaceContext.INSTANCE); ingo@2500: } ingo@2500: ingo@2500: ingo@2500: /** ingo@32: * This method extracts the reachable states of the current artifact. ingo@32: * ingo@32: * @param reachable The reachable states node. ingo@32: * ingo@32: * @return an array with identifiers of reachable states. ingo@32: */ ingo@32: protected static String[] extractReachableStates(Node reachable) { ingo@1367: logger.debug("ArtifactDescriptionFactory.extractReachableStates()"); ingo@32: ingo@32: NodeList list = (NodeList) XMLUtils.xpath( ingo@32: reachable, ingo@32: XPATH_REACHABLE_STATE, ingo@32: XPathConstants.NODESET, ingo@32: ArtifactNamespaceContext.INSTANCE); ingo@32: ingo@32: if (list == null || list.getLength() == 0) { ingo@32: return null; ingo@32: } ingo@32: ingo@32: int count = list.getLength(); ingo@32: ingo@32: String[] states = new String[count]; ingo@32: ingo@32: for (int i = 0; i < count; i++) { ingo@32: Node state = list.item(i); ingo@32: ingo@32: String name = XMLUtils.xpathString( ingo@32: state, "@art:name", ArtifactNamespaceContext.INSTANCE); ingo@32: ingo@32: states[i] = name; ingo@32: } ingo@32: ingo@32: return states; ingo@32: } ingo@65: ingo@65: ingo@65: /** ingo@65: * This method extract available output modes of the the current artifact. ingo@65: * ingo@65: * @param outputs A list of nodes that contain information about output ingo@65: * modes. ingo@65: * ingo@65: * @return an array of Output modes. ingo@65: */ ingo@65: protected static OutputMode[] extractOutputModes(NodeList outputs) { ingo@1367: logger.debug("ArtifactDescriptionFactory.extractOutputModes"); ingo@65: ingo@65: if (outputs == null || outputs.getLength() == 0) { ingo@65: return null; ingo@65: } ingo@65: ingo@65: int size = outputs.getLength(); ingo@65: ingo@65: List outs = new ArrayList(size); ingo@65: ingo@65: for (int i = 0; i < size; i++) { ingo@65: Node out = outputs.item(i); ingo@65: ingo@65: String name = XMLUtils.xpathString( ingo@65: out, "@art:name", ArtifactNamespaceContext.INSTANCE); ingo@65: String desc = XMLUtils.xpathString( ingo@65: out, "@art:description", ArtifactNamespaceContext.INSTANCE); ingo@65: String mimeType = XMLUtils.xpathString( ingo@65: out, "@art:mime-type", ArtifactNamespaceContext.INSTANCE); ingo@65: ingo@65: if (name != null) { ingo@65: outs.add(new DefaultOutputMode(name, desc, mimeType)); ingo@65: } ingo@65: else { ingo@1367: logger.debug("Found an invalid output mode."); ingo@65: } ingo@65: } ingo@65: ingo@65: return (OutputMode[]) outs.toArray(new OutputMode[size]); ingo@65: } ingo@803: ingo@803: ingo@807: protected static Recommendation[] extractRecommendedArtifacts(Document doc){ ingo@1367: logger.debug("ArtifactDescriptionFactory.extractRecommendedArtifacts."); ingo@803: ingo@803: NodeList list = (NodeList) XMLUtils.xpath( ingo@803: doc, ingo@803: XPATH_RECOMMENDED_ARTIFACTS, ingo@803: XPathConstants.NODESET, ingo@803: ArtifactNamespaceContext.INSTANCE); ingo@803: ingo@803: int num = list != null ? list.getLength() : 0; ingo@803: ingo@807: Recommendation[] rec = new Recommendation[num]; ingo@803: ingo@803: for (int i = 0; i < num; i++) { ingo@807: Element e = (Element) list.item(i); ingo@807: String factory = e.getAttribute("factory"); sascha@836: String index = e.getAttribute("ids"); ingo@803: ingo@807: if (factory != null && factory.length() > 0) { sascha@839: rec[i] = new Recommendation(factory, index); ingo@803: } ingo@803: } ingo@803: ingo@807: return rec; ingo@803: } ingo@16: } ingo@16: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :