/******************************************************************************* * ALMA - Atacama Large Millimeter Array * Copyright (c) COSYLAB - Control System Laboratory, 2011 * (in the framework of the ALMA collaboration). * All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *******************************************************************************/ package com.cosylab.cdb.jdal.hibernate; import java.io.IOException; import java.io.StringReader; import java.io.StringWriter; import java.util.HashSet; import java.util.Queue; import java.util.Set; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.logging.Logger; 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 org.w3c.dom.DOMImplementation; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import alma.cdbErrType.CDBFieldDoesNotExistEx; import alma.cdbErrType.WrongCDBDataTypeEx; import com.cosylab.CDB.DAOOperations; public class ExtraDataFeatureUtil { /** * Object pool used for DocumentBuilderObjectPool. * Should probably move to module jacsutil at some point, but then * with additional features like unreferencing objects * above some minimum pool size, when they are stale for a while. */ private static abstract class ObjectPool<T> { private final Queue<T> objects = new ConcurrentLinkedQueue<T>(); public abstract T createObject(); public T borrowObject() { T t; if ((t = objects.poll()) == null) { t = createObject(); } return t; } /** * @param object */ public void returnObject(T object) { this.objects.offer(object); } } /** * We reuse XML parsers from this pool, to avoid thread issues reported in * http://jira.alma.cl/browse/COMP-6488 . */ private static class DocumentBuilderObjectPool extends ObjectPool<DocumentBuilder> { // always thread-safe? Currently no other such calls in the rdbCDB process though. final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); @Override public synchronized DocumentBuilder createObject() { try { return factory.newDocumentBuilder(); } catch (ParserConfigurationException e) { throw new RuntimeException(e); } } } public static final DocumentBuilderObjectPool documentBuilderObjectPool = new DocumentBuilderObjectPool(); public static final Set<String> EMPTY_SET = new HashSet<String>(); private final Logger logger; public ExtraDataFeatureUtil(Logger logger) { this.logger = logger; } /* private static final String removeNamespace(String name) { // only one is allowed, start from beginning (most likely to find it there) int pos = name.indexOf(":"); if (pos == -1) return name; else return name.substring(pos + 1); } */ public static Element getExtraDataMap(String xml) throws ParserConfigurationException, SAXException, IOException { if (xml == null || xml.isEmpty()) return null; DocumentBuilder builder = documentBuilderObjectPool.borrowObject(); try { Document xmldoc = builder.parse(new InputSource(new StringReader(xml))); return xmldoc.getDocumentElement(); } finally { documentBuilderObjectPool.returnObject(builder); } } public String getExtraDataMap(DAOOperations dao, String path, Set<String> expectedAttributes, Set<String> expectedElements) throws CDBFieldDoesNotExistEx, WrongCDBDataTypeEx, ParserConfigurationException, TransformerException { Document xmldoc; DocumentBuilder builder = documentBuilderObjectPool.borrowObject(); try { DOMImplementation impl = builder.getDOMImplementation(); xmldoc = impl.createDocument(null, "data", null); } finally { documentBuilderObjectPool.returnObject(builder); } Element root = xmldoc.getDocumentElement(); if (getExtraDataMap(root, dao, path, expectedAttributes, expectedElements)) { StringWriter stringWriter = new StringWriter(); StreamResult streamResult = new StreamResult(stringWriter); TransformerFactory tf = TransformerFactory.newInstance(); Transformer serializer = tf.newTransformer(); serializer.setOutputProperty(OutputKeys.INDENT,"yes"); serializer.transform(new DOMSource(xmldoc), streamResult); String ret = stringWriter.toString(); // Oracle XMLTYPE attributes don't like empty XML, thus we convert it to null if (ret != null && ret.trim().isEmpty()) { ret = null; } return ret; } else { return null; } } protected static boolean getExtraDataMap(Element root, DAOOperations dao, String path, Set<String> expectedAttributes, Set<String> expectedElements) throws CDBFieldDoesNotExistEx, WrongCDBDataTypeEx { final String prefix = path == null || path.length() == 0 ? "" : path + "/"; boolean changed = false; String[] attributes = dao.get_string_seq(prefix + "_attributes"); for (String attribute : attributes) if (!expectedAttributes.contains(attribute)) { root.setAttribute(/*removeNamespace(*/attribute/*)*/, dao.get_string(prefix + attribute)); changed = true; } String[] elements = dao.get_string_seq(prefix + "_elements"); for (String element : elements) if (!expectedElements.contains(element) && !element.startsWith("xsi:")) { // special case, array of numbers (array of strings not supported) if (Character.isDigit(element.charAt(0))) { Element parentNode = (Element)root.getParentNode(); parentNode.removeChild(root); parentNode.setAttribute(/*removeNamespace(*/root.getNodeName()/*)*/, dao.get_string(path)); changed = true; break; } // artificial name, try to get the real one String elementName = element; // for maps we get Name attribute as element, so there is no easy way to detect this //if (Character.isDigit(element.charAt(element.length()-1))) { try { elementName = dao.get_string(prefix + element + "/_name"); } catch (Throwable th) { // TODO log debug } } Element newElement = root.getOwnerDocument().createElement(/*removeNamespace(*/elementName/*)*/); root.appendChild(newElement); changed = true; getExtraDataMap(newElement, dao, prefix + element, EMPTY_SET, EMPTY_SET); } return changed; } }