001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.camel.converter.jaxp;
018
019import java.util.ArrayList;
020import java.util.List;
021
022import org.w3c.dom.Document;
023import org.w3c.dom.Node;
024import org.w3c.dom.NodeList;
025import org.w3c.dom.Text;
026
027/**
028 * A simple thread-safe {@link NodeList} that is used by {@link org.apache.camel.builder.xml.XPathBuilder}
029 * to return thread-safe {@link NodeList} instances as its result.
030 * <p/>
031 * This is needed to ensure that end users do not hit any concurrency issues while working
032 * with xpath expressions using built-in from the JDK or via camel-saxon.
033 */
034public class ThreadSafeNodeList implements NodeList {
035
036    private final List<Node> list = new ArrayList<>();
037
038    public ThreadSafeNodeList(NodeList source) throws Exception {
039        init(source);
040    }
041
042    @Override
043    public Node item(int index) {
044        return list.get(index);
045    }
046
047    @Override
048    public int getLength() {
049        return list.size();
050    }
051
052    private void init(NodeList source) throws Exception {
053        for (int i = 0; i < source.getLength(); i++) {
054            Node node = source.item(i);
055            if (node != null) {
056                // import node must not occur concurrent on the same node (must be its owner)
057                // so we need to synchronize on it
058                synchronized (node.getOwnerDocument()) {
059                    Document doc = new XmlConverter().createDocument();
060                    // import node must not occur concurrent on the same node (must be its owner)
061                    // so we need to synchronize on it
062                    synchronized (node.getOwnerDocument()) {
063                        Node clone = doc.importNode(node, true);
064                        if (clone instanceof Text) {
065                            // basic text node then add as-is
066                            list.add(clone);
067                        } else {
068                            // more complex node, then add as child (yes its a bit weird but this is working)
069                            doc.appendChild(clone);
070                            list.add(doc.getChildNodes().item(0));
071                        }
072                    }
073                }
074            }
075        }
076    }
077
078}