Java tutorial
/* * dalclient library - provides utilities to assist in using KDDart-DAL servers * Copyright (C) 2015,2016,2017 Diversity Arrays Technology * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.diversityarrays.dalclient; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintStream; import java.io.PrintWriter; import java.io.StringReader; import java.io.UnsupportedEncodingException; import java.io.Writer; import java.math.BigInteger; import java.net.URI; import java.net.URLDecoder; import java.net.URLEncoder; import java.security.DigestInputStream; import java.security.InvalidKeyException; import java.security.KeyManagementException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collection; import java.util.Formatter; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import org.apache.commons.codec.binary.Hex; import org.apache.commons.collections15.Predicate; import org.w3c.dom.Document; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import com.diversityarrays.dalclient.http.DalCloseableHttpClient; import com.diversityarrays.dalclient.http.DalCloseableHttpResponse; import com.diversityarrays.dalclient.http.DalHeader; import com.diversityarrays.dalclient.http.DalRequest; import com.diversityarrays.dalclient.http.DalResponseHandler; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.gson.JsonPrimitive; import com.google.gson.JsonSyntaxException; /** * Utility routines for use across the rest of the DAL packages. * @author brian * */ @SuppressWarnings("nls") public class DalUtil { private static final String DIGEST_MD5 = "MD5"; //$NON-NLS-1$ private static final String ALGORITHM_HMAC_SHA1 = "HmacSHA1"; //$NON-NLS-1$ public static final String ENCODING_UTF_8 = "UTF-8"; //$NON-NLS-1$ /** * Get the version number of the DAL Client library. * @return String * @since 2.0 */ static public String getDalClientLibraryVersion() { return Main.VERSION; } static public boolean DEBUG = Boolean.getBoolean(DalUtil.class.getName() + ".DEBUG"); //$NON-NLS-1$ /** * Use this with SimpleDateFormat for date/time values sent to DAL. */ static public final String DATE_FORMAT_STRING = "yyyy-MM-dd HH:mm:ss"; //$NON-NLS-1$ /** * This is the beginning of the standard DAL "permission denied" error. * (see getDalErrorMessage()) */ static public final String PERMISSION_DENIED_LOCASE_STEM = "permission denied"; //$NON-NLS-1$ /** * This is the Charset name used for crypto. */ static public String cryptCharsetName = ENCODING_UTF_8; /** * Compare two version number strings and return * -1, 0, 1 if <code>a</code> is respectively less-than, equal to * or greater-than <code>b</code>. * @param a * @param b * @return -1, 0 or 1 */ static public int compareVersions(String a, String b) { Pattern pattern = Pattern.compile("^([0-9]+)"); //$NON-NLS-1$ StringTokenizer st_a = new StringTokenizer(a, "."); //$NON-NLS-1$ StringTokenizer st_b = new StringTokenizer(b, "."); //$NON-NLS-1$ while (st_a.hasMoreTokens() && st_b.hasMoreTokens()) { Matcher m = pattern.matcher(st_a.nextToken()); Integer anum = 0; if (m.matches()) { anum = new Integer(m.group(1)); } Integer bnum = 0; m = pattern.matcher(st_b.nextToken()); if (m.matches()) { bnum = new Integer(m.group(1)); } int result = anum.compareTo(bnum); if (result != 0) { return result; } } if (st_a.hasMoreTokens()) { // well then b doesn't return +1; } if (st_b.hasMoreTokens()) { return -1; } return 0; } /** * Create an SSLContext which will trust all certificates (i.e. it does not validate * any certificate chains). * @return an SSLContext */ static public SSLContext createTrustingSSLContext() { // Create a trust manager that does not validate certificate chains final TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() { @Override public void checkClientTrusted(final X509Certificate[] chain, final String authType) { } @Override public void checkServerTrusted(final X509Certificate[] chain, final String authType) { } @Override public X509Certificate[] getAcceptedIssuers() { return null; } } }; try { final SSLContext sslContext = SSLContext.getInstance("SSL"); //$NON-NLS-1$ // Install the all-trusting trust manager sslContext.init(null, trustAllCerts, new java.security.SecureRandom()); return sslContext; } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } catch (KeyManagementException e) { throw new RuntimeException(e); } } /** * Convenience interface to URLEncoder.encode(input, "UTF-8"). * @param input * @return the encoded String */ static public String urlEncodeUTF8(String input) { String result = input; try { result = URLEncoder.encode(input, ENCODING_UTF_8); } catch (UnsupportedEncodingException e) { } return result; } /** * Convenience interface to URLDecoder.decode(input, "UTF-8"). * @param input * @return the decoded String */ static public String urlDecodeUTF8(String input) { String result = input; try { result = URLDecoder.decode(input, ENCODING_UTF_8); } catch (UnsupportedEncodingException e) { } return result; } /** * Replace all of the parameters in the provided commandTemplate with * the values of the parameters. The commandPattern portions are "/" delimited and * parameters names are those segments which begin with the letter "_". * <p> * An alternative is to use the CommandBuilder class. * @param prefix prepended to the URL constructed, may be null * @param commandTemplate * @param parameters * @return the URL * @throws DalMissingParameterException */ static public String buildCommand(String prefix, String commandTemplate, Map<String, String> parameters) throws DalMissingParameterException { StringBuilder sb = new StringBuilder(); String sep = prefix == null ? "" : prefix; //$NON-NLS-1$ for (String p : commandTemplate.split("/")) { //$NON-NLS-1$ sb.append(sep); sep = "/"; //$NON-NLS-1$ if (p.startsWith("_")) { //$NON-NLS-1$ String v = parameters.get(p); if (v == null) { throw new DalMissingParameterException( String.format("Missing value for '%s' in command: %s", p, commandTemplate)); //$NON-NLS-1$ } sb.append(v); } else { sb.append(p); } } return sb.toString(); } /** * <p> * Execute the provided request using the client and allow the handler to process the response. * Before returning the result from the handler, ensure that the response has been closed. * <p> * This is basically a wrapper around the similarly named method with a Long[] as the last parameter. * @param client * @param request * @param handler * @return the result provided by the handler * @throws IOException */ static public <T> T doHttp(DalCloseableHttpClient client, DalRequest request, DalResponseHandler<T> handler) throws IOException { return doHttp(client, request, handler, null); } /** * Execute the provided request using the client and allow the handler to process the response. * Before returning the result from the handler, ensure that the response has been closed. * If the <code>elapsedTime</code> parameter is supplied then return the number of milliseconds * spent in the <code>client.execute()</code> method call. * @param client * @param request * @param handler * @param elapsedTimeMillis if non-null it will receive the elapsed time of the actual time spent in the <code>client.execute()</code> method * @return the result provided by the handler * @throws IOException */ static public <T> T doHttp(DalCloseableHttpClient client, DalRequest request, DalResponseHandler<T> handler, Long[] elapsedTimeMillis) throws IOException { T result = null; DalCloseableHttpResponse response = null; try { if (DEBUG) { URI uri = request.getURI(); System.err.println(DalUtil.class.getSimpleName() + ".doHttp: " + uri.toString()); //$NON-NLS-1$ System.err.println(" --- Headers ---"); //$NON-NLS-1$ for (DalHeader h : request.getAllHeaders()) { System.err.println("\t" + h.getName() + ":\t" + h.getValue()); //$NON-NLS-1$ //$NON-NLS-2$ } } long startMillis = System.currentTimeMillis(); response = client.execute(request); if (elapsedTimeMillis != null) { elapsedTimeMillis[0] = System.currentTimeMillis() - startMillis; } result = handler.handleResponse(response); } finally { if (response != null) { try { response.close(); } catch (IOException ignore) { } } } return result; } /** * Calculate an RFC 2104 compliant HMAC signature. * @param key is the signing key * @param data is the data to be signed * @return the base64-encoded signature as a String */ public static String computeHmacSHA1(String key, String data) { try { byte[] keyBytes = key.getBytes(cryptCharsetName); SecretKeySpec signingKey = new SecretKeySpec(keyBytes, ALGORITHM_HMAC_SHA1); Mac mac = Mac.getInstance(ALGORITHM_HMAC_SHA1); mac.init(signingKey); byte[] rawHmac = mac.doFinal(data.getBytes(cryptCharsetName)); byte[] hexBytes = new Hex().encode(rawHmac); return new String(hexBytes, cryptCharsetName); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } catch (InvalidKeyException e) { throw new RuntimeException(e); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } /** * Generate a 64-bit random number. * @return the number as a String */ static public String createRandomNumberString() { SecureRandom random = new SecureRandom(); byte[] sixtyFourBits = new byte[8]; random.nextBytes(sixtyFourBits); // Let's be positive about this :-) sixtyFourBits[0] = (byte) (0x7f & sixtyFourBits[0]); BigInteger bigint = new BigInteger(sixtyFourBits); return bigint.toString(); } /** * Computes the MD5 checksum of the bytes in the InputStream. * The input is close()d on exit. * @param input is the InputStream for which to compute the checksum * @return the MD5 checksum as a String of hexadecimal characters */ static public String computeMD5checksum(InputStream input) { DigestInputStream dis = null; Formatter formatter = null; try { MessageDigest md = MessageDigest.getInstance(DIGEST_MD5); dis = new DigestInputStream(input, md); while (-1 != dis.read()) ; byte[] digest = md.digest(); formatter = new Formatter(); for (byte b : digest) { formatter.format("%02x", b); //$NON-NLS-1$ } return formatter.toString(); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } finally { if (dis != null) { try { dis.close(); } catch (IOException ignore) { } } if (formatter != null) { formatter.close(); } } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // JSON tools /** * <p> * This interface is used to allow substitution of a different json parser as/if required. * <p> * The input JSON string is expected to be of the form: * <pre> * { * key-a: ... , * key-b: ... , * recordName: [ * { attr-1: value-1, attr-2: value-2, fieldName: valueWanted, attr-4: value-4 ... }, * { ... }, * ... * ], * ... * } * </pre> */ static public interface JsonResult { List<?> getListAt(String recordName); /** * Extract the "Error" message from the JSON response. * Note that if the Message attribute is missing, this will still produce * an error. * <p> * Any other attributes of the <i>Error</i> element will also be returned. This caters * for diagnostic errors which are returned by some of the DAL "update" operations. * * @return the error message or null */ String getJsonlDalErrorMessage(); /** * Retrieve the value of the key 'fieldName' from the first * array element of the key 'recordName' in this JsonResult. * @param recordName * @param fieldName * @return a String or null * @throws DalResponseFormatException */ String getJsonRecordFieldValue(String recordName, String fieldName) throws DalResponseFormatException; /** * Return the first JSON array element from the value of the supplied key. * @param key * @return a Map, an empty one if the key doesn't exist or the array is empty * @throws DalResponseFormatException */ DalResponseRecord getFirstRecord(String key) throws DalResponseFormatException; /** * Invoke the <code>visitor.visitResponseRow()</code> method for each result in the response. * @param visitor * @param wantedTagNames * @return true if all records were visited * @throws DalResponseException */ boolean visitResults(DalResponseRecordVisitor visitor, List<String> wantedTagNames) throws DalResponseException; /** * Invoke the <code>visitor.visitResponseRow()</code> method for each result in the response. * @param visitor * @param wantedTagNames * @param wantEmptyRecords * @return true if all records were visited * @throws DalResponseException */ boolean visitResults(DalResponseRecordVisitor visitor, List<String> wantedTagNames, boolean wantEmptyRecords) throws DalResponseException; } static public class JsonResultImpl implements JsonResult { private final String requestUrl; private final JsonObject jsonObject; public JsonResultImpl(String requestUrl, JsonObject jsonObject) { this.requestUrl = requestUrl; this.jsonObject = jsonObject; } @SuppressWarnings({ "rawtypes", "unchecked" }) @Override public List<?> getListAt(String recordName) { List result = null; JsonElement elt = jsonObject.get(recordName); if (elt != null && elt.isJsonArray()) { JsonArray array = (JsonArray) elt; result = new ArrayList<>(array.size()); for (JsonElement arrayElt : array) { result.add(arrayElt); } } return result; } @Override public String getJsonlDalErrorMessage() { String result = null; List<?> list = getListAt(DALClient.TAG_ERROR); if (list != null) { if (list.isEmpty()) { result = "Unknown error: missing element"; //$NON-NLS-1$ } else { Object item = list.get(0); if (item == null) { result = "Unknown error: 'null'"; //$NON-NLS-1$ } else if (item instanceof JsonObject) { JsonObject errors = (JsonObject) item; StringBuilder sb = new StringBuilder(); String sep = ""; //$NON-NLS-1$ for (Map.Entry<String, JsonElement> entry : errors.entrySet()) { JsonElement value = entry.getValue(); sb.append(sep).append(entry.getKey()).append('=').append(value.getAsString()); } result = sb.toString(); } else { result = "Unknown error: 'Error' item is " + item.getClass().getName(); //$NON-NLS-1$ } } } return result; } @Override public String getJsonRecordFieldValue(String recordName, String fieldName) throws DalResponseFormatException { String result = null; List<?> list = getListAt(recordName); // we expect a particular structure in a DAL response if (list != null) { if (!list.isEmpty()) { Object info = list.get(0); if (info instanceof JsonObject) { JsonObject jsonObject = (JsonObject) info; JsonElement value = jsonObject.get(fieldName); result = value.getAsString(); } else { throw new DalResponseFormatException( String.format("Expected JsonObject but got %s", info.getClass().getName())); } } } return result; } @Override public DalResponseRecord getFirstRecord(String key) throws DalResponseFormatException { DalResponseRecord result = null; List<?> list = getListAt(key); if (list != null && !list.isEmpty()) { Object item = list.get(0); if (item instanceof JsonObject) { result = createFrom(requestUrl, key, (JsonObject) item); } else { throw new DalResponseFormatException( String.format("unexpected type for '%s'[0] : %s", key, item.getClass().getName())); //$NON-NLS-1$ } } return result == null ? new DalResponseRecord(requestUrl, key) : result; } @Override public boolean visitResults(DalResponseRecordVisitor visitor, List<String> wantedTagNames) throws DalResponseException { return visitResults(visitor, wantedTagNames, false); } @Override public boolean visitResults(DalResponseRecordVisitor visitor, List<String> wantedTagNames, boolean wantEmptyRecords) throws DalResponseException { boolean result = true; List<?> rmetaList = getListAt(DALClient.TAG_RECORD_META); if (rmetaList == null || rmetaList.isEmpty()) { throw new DalResponseException("missing RecordMeta in DAL response"); //$NON-NLS-1$ } for (Object rmeta : rmetaList) { if (!(rmeta instanceof JsonObject)) { throw new DalResponseException("Invalid JSON structure in " + DALClient.TAG_RECORD_META); } JsonObject rmetaMap = (JsonObject) rmeta; JsonElement tagnameObj = rmetaMap.get(DALClient.ATTR_TAG_NAME); if (tagnameObj == null) { throw new DalResponseException("missing RecordMeta/TagName in DAL response"); } if (!tagnameObj.isJsonPrimitive()) { throw new DalResponseException( String.format("Invalid JSON structure for RecordMeta/TagName in DAL response: %s", tagnameObj == null ? "null" : tagnameObj.getClass().getName())); } String tagName = tagnameObj.getAsString(); List<?> list = getListAt(tagName); if (list == null) { throw new DalResponseException( String.format("missing entry for '%s' in DAL response", tagName)); //$NON-NLS-1$ } int count = 0; for (Object item : list) { if (item instanceof JsonObject) { JsonObject jsonObject = (JsonObject) item; DalResponseRecord record = createFrom(requestUrl, tagName, jsonObject); if (wantEmptyRecords || !record.isEmpty()) { if (!visitor.visitResponseRecord(tagName, record)) { result = false; break; } } } else { throw new DalResponseFormatException(String.format("unexpected type for '%s'[%d] :%s", //$NON-NLS-1$ tagName, count, item.getClass().getName())); } ++count; } if (!result) { break; } } return result; } } /** * Parse the input json string and return the parse result if it is a JsonObject. * This is because a valid DAL JSON response is always and only of that structure. * @param json * @return either JsonResult or null if the input cannot be parsed * @throws DalResponseFormatException */ public static JsonResult parseJson(String requestUrl, String json) throws DalResponseFormatException { JsonResult result = null; try { JsonElement jsonElement = new JsonParser().parse(json); if (!jsonElement.isJsonObject()) { throw new DalResponseFormatException( String.format("input is not a JsonObject: %s", jsonElement.getClass().getName())); } result = new JsonResultImpl(requestUrl, (JsonObject) jsonElement); } catch (JsonSyntaxException e) { throw new DalResponseFormatException(e); } return result; } static public DalResponseRecord createFrom(String requestUrl, String tagName, JsonObject input) { DalResponseRecord result = new DalResponseRecord(requestUrl, tagName); for (Map.Entry<String, JsonElement> entry : input.entrySet()) { String key = entry.getKey(); JsonElement value = entry.getValue(); if (value == null || value.isJsonNull()) { result.rowdata.put(key, ""); } else if (value.isJsonArray()) { JsonArray array = (JsonArray) value; int count = 0; for (JsonElement elt : array) { if (elt != null && elt.isJsonObject()) { JsonObject child = (JsonObject) elt; Map<String, String> childMap = asRowdata(child); if (childMap != null) { result.addNestedData(key, childMap); } } else { result.warnings.add(String.format("unexpected value-type for '%s'[%d] :%s", //$NON-NLS-1$ key, count, elt == null ? "null" : elt.getClass().getName())); } ++count; } } else if (value.isJsonPrimitive()) { JsonPrimitive prim = (JsonPrimitive) value; result.rowdata.put(key, prim.getAsString()); } else if (value.isJsonObject()) { // ?? perhaps Map<String, String> childMap = asRowdata((JsonObject) value); if (childMap != null) { result.addNestedData(key, childMap); } } else { result.warnings.add(String.format("unexpected value-type for '%s' :%s", //$NON-NLS-1$ key, value.getClass().getName())); } } return result; } /** * Convert the the input into a Map<String,String>. * @param input * @return a Map<String,String> */ static private Map<String, String> asRowdata(JsonObject input) { Map<String, String> result = null; Set<Map.Entry<String, JsonElement>> entrySet = input.entrySet(); if (entrySet != null && !entrySet.isEmpty()) { for (Map.Entry<String, JsonElement> entry : entrySet) { if (result == null) { result = new LinkedHashMap<>(); } String key = entry.getKey(); JsonElement value = entry.getValue(); if (value == null || value.isJsonNull()) { result.put(key, ""); } else { result.put(key, value.getAsString()); } } } return result; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // XML tools public static Document createXmlDocument(String xml) throws ParserConfigurationException, SAXException, IOException { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); InputSource is = new InputSource(new StringReader(xml)); Document xmldoc = builder.parse(is); return xmldoc; } public static boolean visitXmlResults(String requestUrl, Document xmldoc, List<String> tagNames, DalResponseRecordVisitor visitor) { return visitXmlResults(requestUrl, xmldoc, tagNames, visitor, false); } public static boolean visitXmlResults(String requestUrl, Document xmldoc, List<String> tagNames, DalResponseRecordVisitor visitor, boolean wantEmptyRecords) { boolean result = true; for (String tagName : tagNames) { NodeList resultNodes = xmldoc.getElementsByTagName(tagName); int nNodes = resultNodes.getLength(); for (int ni = 0; ni < nNodes; ++ni) { Node node = resultNodes.item(ni); DalResponseRecord record = DalUtil.createFrom(requestUrl, node); if (record != null && (wantEmptyRecords || !record.isEmpty())) { if (!visitor.visitResponseRecord(tagName, record)) { result = false; break; } } } if (!result) { break; } } return result; } public static DalResponseRecord createFrom(String requestUrl, Node node) { DalResponseRecord result = new DalResponseRecord(requestUrl, node.getNodeName()); NamedNodeMap attributes = node.getAttributes(); if (attributes != null) { int nAttributes = attributes.getLength(); for (int ai = 0; ai < nAttributes; ++ai) { Node attr = attributes.item(ai); result.rowdata.put(attr.getNodeName(), attr.getNodeValue()); } } NodeList childNodes = node.getChildNodes(); int nChildNodes = childNodes.getLength(); if (nChildNodes > 0) { for (int ci = 0; ci < nChildNodes; ++ci) { Node child = childNodes.item(ci); if (Node.ELEMENT_NODE == child.getNodeType()) { String childName = child.getNodeName(); Map<String, String> childMap = asRowdata(child); if (childMap != null) { result.addNestedData(childName, childMap); } } } } return result; } public static Map<String, String> asRowdata(Node node) { Map<String, String> result = null; NamedNodeMap attributes = node.getAttributes(); if (attributes != null) { int nAttributes = attributes.getLength(); result = new LinkedHashMap<>(nAttributes); for (int ai = 0; ai < nAttributes; ++ai) { Node attr = attributes.item(ai); result.put(attr.getNodeName(), attr.getNodeValue()); } } return result; } public static void showXmlResult(String xml, OutputStream out) { if (looksLikeDoctype(xml)) { // just print it! PrintStream ps = new PrintStream(out); ps.print(xml); ps.close(); } else { try { showXmlResult(xml, new OutputStreamWriter(out, ENCODING_UTF_8)); } catch (UnsupportedEncodingException e) { throw new RuntimeException("Should never happen!", e); //$NON-NLS-1$ } } } public static void showXmlResult(String xml, Writer w) { if (looksLikeDoctype(xml)) { // just print it! PrintWriter pw = new PrintWriter(w); pw.print(xml); pw.close(); } else { try { writeXmlResult(xml, w); } catch (IOException e) { e.printStackTrace(); } catch (TransformerException e) { e.printStackTrace(); } } } public static void writeXmlViaDocument(String xml, Writer w) { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); try { DocumentBuilder builder = factory.newDocumentBuilder(); InputSource is = new InputSource(new StringReader(xml)); Document xmldoc = builder.parse(is); printXmlDocument(xmldoc, w); } catch (ParserConfigurationException e) { e.printStackTrace(); } catch (SAXException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (TransformerException e) { e.printStackTrace(); } } public static void writeXmlResult(String xml, Writer w) throws IOException, TransformerException { StreamSource source = new StreamSource(new StringReader(xml)); TransformerFactory tf = TransformerFactory.newInstance(); Transformer transformer = tf.newTransformer(); transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); //$NON-NLS-1$ transformer.setOutputProperty(OutputKeys.METHOD, "xml"); //$NON-NLS-1$ transformer.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$ transformer.setOutputProperty(OutputKeys.ENCODING, ENCODING_UTF_8); transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); //$NON-NLS-1$ //$NON-NLS-2$ transformer.transform(source, new StreamResult(w)); } public static void printXmlDocument(Document doc, Writer w) throws IOException, TransformerException { TransformerFactory tf = TransformerFactory.newInstance(); Transformer transformer = tf.newTransformer(); transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); //$NON-NLS-1$ transformer.setOutputProperty(OutputKeys.METHOD, "xml"); //$NON-NLS-1$ transformer.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$ transformer.setOutputProperty(OutputKeys.ENCODING, ENCODING_UTF_8); transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); //$NON-NLS-1$ //$NON-NLS-2$ transformer.transform(new DOMSource(doc), new StreamResult(w)); } /** * If the Throwable parameter is an instance of DalResponseHttpException, * extract the error message from it. Otherwise return null. * @param t * @return the String error message from DalResponseHttpException or null */ static public String extractPossibleDalErrorMessage(Throwable t) { String errmsg = null; if (t instanceof DalResponseHttpException) { DalResponseHttpException he = (DalResponseHttpException) t; System.out.println("serverResponse=" + he.responseInfo.serverResponse); //$NON-NLS-1$ System.out.println("dalErrorMessage='" + he.dalErrorMessage + "'"); //$NON-NLS-1$ //$NON-NLS-2$ Matcher m = Pattern.compile("^.*Message=(.*)$").matcher(he.dalErrorMessage); //$NON-NLS-1$ if (m.matches()) { System.out.println("Message is '" + m.group(1) + "'"); //$NON-NLS-1$ //$NON-NLS-2$ errmsg = m.group(1); } else { errmsg = he.dalErrorMessage; } } return errmsg; } /** * <p> * Extract the <Error Message="..." /> from the XML response. * In fact it will retrieve all of the attributes from the 'Error' element * because some DAL responses use the attribute name as part of the message! * <p> * Note that if the Message attribute is missing, this will still produce * an error so it is <b>not</b> the same as just calling: * <code> * getElementAttributeValue(doc, "Error", "Message"); * </code> * <br> * Any other attributes of the <i>Error</i> element will also be returned. This caters * for diagnostic errors which are returned by some of the DAL "update" operations. * @param doc * @return error message as a String or null */ static public String getXmlDalErrorMessage(Document doc) { String result = null; NodeList errorElements = doc.getElementsByTagName(DALClient.TAG_ERROR); if (errorElements.getLength() > 0) { Node item = errorElements.item(0); NamedNodeMap attributes = item.getAttributes(); if (attributes == null) { result = String.format("No attributes available in '%s' element", //$NON-NLS-1$ DALClient.TAG_ERROR); } else { // We will get them *all*. StringBuilder sb = new StringBuilder(); String sep = ""; //$NON-NLS-1$ int nAttributes = attributes.getLength(); for (int ai = 0; ai < nAttributes; ++ai) { Node attr = attributes.item(ai); if (attr != null) { sb.append(sep).append(attr.getNodeName()).append('=').append(attr.getTextContent()); sep = ", "; //$NON-NLS-1$ } } result = sb.toString(); } } return result; } /** * Return the named attribute from the specified element in the (XML) document. * @param doc * @param tagName * @param attributeName * @return the attribute value or null if either the element or the attribute are missing */ static public String getElementAttributeValue(Document doc, String tagName, String attributeName) { return getAttributeValue(doc.getElementsByTagName(tagName), attributeName); } /** * Return the value of the named attribute from the first Node in the NodeList * or null if the NodeList is empty or the attribute is not present. * @param elements * @param attributeName * @return a String or null */ static public String getAttributeValue(NodeList elements, String attributeName) { String result = null; if (elements != null && elements.getLength() > 0) { Node item = elements.item(0); NamedNodeMap attributes = item.getAttributes(); if (attributes != null) { Node attr = attributes.getNamedItem(attributeName); if (attr != null) { result = attr.getNodeValue(); } } } return result; } public static List<DalResponseRecord> collectResponseRecords(DalResponse response) throws DalResponseFormatException, DalResponseException { return collectResponseRecords(response, (Predicate<String>) null); } public static List<DalResponseRecord> collectResponseRecords(DalResponse response, final Predicate<String> tagNamePredicate) throws DalResponseFormatException, DalResponseException { final List<DalResponseRecord> result = new ArrayList<>(); response.visitResults(new DalResponseRecordVisitor() { @Override public boolean visitResponseRecord(String resultTagName, DalResponseRecord data) { if (tagNamePredicate == null || tagNamePredicate.evaluate(resultTagName)) { result.add(data); } return true; } }); return result; } // public static List<DalResponseRecord> collectResponseRecords(DalResponse response, // final java.util.function.Predicate<String> tagNamePredicate) // throws DalResponseFormatException, DalResponseException // { // final List<DalResponseRecord> result = new ArrayList<DalResponseRecord>(); // response.visitResults(new DalResponseRecordVisitor() { // @Override // public boolean visitResponseRecord(String resultTagName, DalResponseRecord data) { // if (tagNamePredicate==null || tagNamePredicate.test(resultTagName)) { // result.add(data); // } // return true; // } // }); // return result; // } /** * Check if the input appears to be a DOCTYPE response. * @param input * @return true or false */ public static boolean looksLikeDoctype(String input) { // this should handle the "<!ENTITY" form currently seen as well as potentially future "<!DOCTYPE" return input != null && input.startsWith("<!"); //$NON-NLS-1$ } public static boolean isHttpStatusCodeOk(int httpStatusCode) { return httpStatusCode >= 200 && httpStatusCode < 300; } static private enum SplitState { LOOKING_FOR_SEPARATOR, IN_QUOTE, LOOKING_FOR_SECOND; } static public String[] splitCsvLine(String line, char columnSeparator, char quoteCharacter) { return splitCsvLine(line, columnSeparator, quoteCharacter, null); } static public String[] splitCsvLine(String line, char columnSeparator, char quoteCharacter, String[] headings) { // Short circuit if no quote characters in the line if (line.indexOf(quoteCharacter) < 0) { return line.split(Pattern.quote(Character.toString(columnSeparator)), -1); } List<String> result = headings == null ? new ArrayList<String>() : new ArrayList<String>(headings.length); int lineLength = line.length(); StringBuilder field = new StringBuilder(); SplitState state = SplitState.LOOKING_FOR_SEPARATOR; for (int i = 0; i < lineLength; ++i) { char ch = line.charAt(i); if (state == SplitState.LOOKING_FOR_SEPARATOR) { if (ch == columnSeparator) { result.add(field.toString()); field.setLength(0); } else { if (field.length() == 0 && ch == quoteCharacter) { // Only quote characters after the column separator are recognized // as the beginning of a quoted string... state = SplitState.IN_QUOTE; } else { field.append(ch); } } } else if (state == SplitState.IN_QUOTE) { if (ch == quoteCharacter) { state = SplitState.LOOKING_FOR_SECOND; } else { field.append(ch); } } else if (state == SplitState.LOOKING_FOR_SECOND) { if (ch == quoteCharacter) { // Doubled quote - we'll keep it and keep looking... field.append(quoteCharacter); state = SplitState.IN_QUOTE; } else if (ch == columnSeparator) { // Actually, we've reached the end of the field ! result.add(field.toString()); field.setLength(0); state = SplitState.LOOKING_FOR_SEPARATOR; } } } result.add(field.toString()); return result.toArray(new String[result.size()]); } static public String join(String sep, Object... parts) { StringBuilder sb = new StringBuilder(); String s = ""; //$NON-NLS-1$ for (Object o : parts) { sb.append(s); if (o != null) { sb.append(o); } s = sep; } return sb.toString(); } static public String join(String sep, Collection<?> parts) { StringBuilder sb = new StringBuilder(); String s = ""; //$NON-NLS-1$ for (Object o : parts) { sb.append(s); if (o != null) { sb.append(o); } s = sep; } return sb.toString(); } }