View Javadoc
1   /*
2    * (c) Copyright 2006-2020 by rapiddweller GmbH & Volker Bergmann. All rights reserved.
3    *
4    * Redistribution and use in source and binary forms, with or without
5    * modification, is permitted under the terms of the
6    * GNU General Public License.
7    *
8    * For redistributing this software or a derivative work under a license other
9    * than the GPL-compatible Free Software License as defined by the Free
10   * Software Foundation or approved by OSI, you must first obtain a commercial
11   * license to this software product from rapiddweller GmbH & Volker Bergmann.
12   *
13   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
14   * WITHOUT A WARRANTY OF ANY KIND. ALL EXPRESS OR IMPLIED CONDITIONS,
15   * REPRESENTATIONS AND WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF
16   * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE
17   * HEREBY EXCLUDED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
18   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24   * POSSIBILITY OF SUCH DAMAGE.
25   */
26  
27  package com.rapiddweller.platform.xml;
28  
29  import com.rapiddweller.benerator.engine.BeneratorContext;
30  import com.rapiddweller.benerator.storage.AbstractStorageSystem;
31  import com.rapiddweller.common.CollectionUtil;
32  import com.rapiddweller.common.Context;
33  import com.rapiddweller.common.NullSafeComparator;
34  import com.rapiddweller.common.collection.OrderedNameMap;
35  import com.rapiddweller.common.context.ContextAware;
36  import com.rapiddweller.common.xml.XMLUtil;
37  import com.rapiddweller.common.xml.XPathUtil;
38  import com.rapiddweller.format.DataSource;
39  import com.rapiddweller.format.util.DataSourceFromIterable;
40  import com.rapiddweller.model.data.ComplexTypeDescriptor;
41  import com.rapiddweller.model.data.Entity;
42  import com.rapiddweller.model.data.TypeDescriptor;
43  import com.rapiddweller.script.PrimitiveType;
44  import org.apache.logging.log4j.LogManager;
45  import org.apache.logging.log4j.Logger;
46  import org.w3c.dom.Document;
47  import org.w3c.dom.Element;
48  import org.w3c.dom.Node;
49  import org.w3c.dom.NodeList;
50  
51  import javax.xml.xpath.XPathExpressionException;
52  import java.io.File;
53  import java.io.FileNotFoundException;
54  import java.io.IOException;
55  import java.util.ArrayList;
56  import java.util.List;
57  
58  /**
59   * Loads an XML document into a DOM structure, supports queries for data and updates.
60   * When an instance is {@link #close()}d, the tree content is written to the {@link #outputUri}.<br/><br/>
61   * Created: 08.01.2014 15:27:38
62   *
63   * @author Volker Bergmann
64   * @since 0.9.0
65   */
66  public class DOMTree extends AbstractStorageSystem implements ContextAware {
67  
68    private static final Logger LOGGER = LogManager.getLogger(DOMTree.class);
69  
70    private String id;
71    private String inputUri;
72    private String outputUri;
73    private boolean namespaceAware;
74  
75    private Context context;
76    private Document document;
77    private final OrderedNameMap<ComplexTypeDescriptor> types;
78  
79  
80    /**
81     * Instantiates a new Dom tree.
82     */
83    public DOMTree() {
84      this(null, null);
85    }
86  
87    /**
88     * Instantiates a new Dom tree.
89     *
90     * @param inOutUri the in out uri
91     * @param context  the context
92     */
93    public DOMTree(String inOutUri, BeneratorContext context) {
94      this.id = inOutUri;
95      this.inputUri = inOutUri;
96      this.outputUri = inOutUri;
97      this.namespaceAware = true;
98      this.document = null;
99      this.types = OrderedNameMap.createCaseInsensitiveMap();
100     setContext(context);
101   }
102 
103   private static String normalizeEncoding(String encoding) {
104     if (encoding.startsWith("UTF-16")) {
105       encoding = "UTF-16";
106     }
107     return encoding;
108   }
109 
110   @Override
111   public String getId() {
112     return id;
113   }
114 
115   /**
116    * Sets id.
117    *
118    * @param id the id
119    */
120   public void setId(String id) {
121     this.id = id;
122   }
123 
124   /**
125    * Gets input uri.
126    *
127    * @return the input uri
128    */
129   public String getInputUri() {
130     return inputUri;
131   }
132 
133   /**
134    * Sets input uri.
135    *
136    * @param inputUri the input uri
137    */
138   public void setInputUri(String inputUri) {
139     this.inputUri = inputUri;
140   }
141 
142   /**
143    * Gets output uri.
144    *
145    * @return the output uri
146    */
147   public String getOutputUri() {
148     return outputUri;
149   }
150 
151   /**
152    * Sets output uri.
153    *
154    * @param outputUri the output uri
155    */
156   public void setOutputUri(String outputUri) {
157     this.outputUri = outputUri;
158   }
159 
160   /**
161    * Is namespace aware boolean.
162    *
163    * @return the boolean
164    */
165   public boolean isNamespaceAware() {
166     return namespaceAware;
167   }
168 
169   /**
170    * Sets namespace aware.
171    *
172    * @param namespaceAware the namespace aware
173    */
174   public void setNamespaceAware(boolean namespaceAware) {
175     this.namespaceAware = namespaceAware;
176   }
177 
178   @Override
179   public void setContext(Context context) {
180     this.context = context;
181     if (context instanceof BeneratorContext) {
182       setDataModel(((BeneratorContext) context).getDataModel());
183     }
184   }
185 
186   @Override
187   public DataSource<Entity> queryEntities(String type, String selector,
188                                           Context context) {
189     beInitialized();
190     LOGGER.debug("queryEntities({}, {}, context)", type, selector);
191     try {
192       NodeList nodes = XPathUtil.queryNodes(document, selector);
193       LOGGER.debug("queryEntities() found {} results", nodes.getLength());
194       List<Entity> list = new ArrayList<>(nodes.getLength());
195       for (int i = 0; i < nodes.getLength(); i++) {
196         Element element = (Element) nodes.item(i);
197         Entity entity =
198             XMLPlatformUtil.convertElement2Entity(element, this);
199         list.add(entity);
200       }
201       return new DataSourceFromIterable<>(list, Entity.class);
202     } catch (XPathExpressionException e) {
203       throw new RuntimeException(
204           "Error querying " + (type != null ? type : "") +
205               " elements with xpath: " + selector, e);
206     }
207   }
208 
209   @Override
210   public DataSource<?> queryEntityIds(String type, String selector,
211                                       Context context) {
212     throw new UnsupportedOperationException(getClass().getSimpleName() +
213         " does not support queries for entity ids");
214   }
215 
216   @Override
217   public DataSource<?> query(String selector, boolean simplify,
218                              Context context) {
219     beInitialized();
220     LOGGER.debug("query({}, {}, context)", selector, simplify);
221     try {
222       NodeList nodes = XPathUtil.queryNodes(document, selector);
223       LOGGER.debug("query() found {} results", nodes.getLength());
224       List<Object> list = new ArrayList<>(nodes.getLength());
225       for (int i = 0; i < nodes.getLength(); i++) {
226         Node node = nodes.item(i);
227         list.add(node.getTextContent());
228       }
229       return new DataSourceFromIterable<>(list, Object.class);
230     } catch (XPathExpressionException e) {
231       throw new RuntimeException(
232           "Error querying items with xpath: " + selector, e);
233     }
234   }
235 
236   @Override
237   public void store(Entity entity) {
238     throw new UnsupportedOperationException(getClass().getSimpleName() +
239         " does not support storing entities");
240   }
241 
242   @Override
243   public void update(Entity entity) {
244     beInitialized();
245     if (entity instanceof XmlEntity) {
246       XMLPlatformUtil.mapEntityToElement(entity,
247           ((XmlEntity) entity).getSourceElement());
248     } else {
249       throw new UnsupportedOperationException(getClass().getSimpleName() +
250           " cannot update entities from other sources");
251     }
252   }
253 
254   @Override
255   public void flush() {
256     // nothing to do
257   }
258 
259   @Override
260   public void close() {
261     if (document != null) {
262       save();
263     } else {
264       // if document is null, loading has failed and there already has been an error message
265     }
266   }
267 
268   @Override
269   public TypeDescriptor[] getTypeDescriptors() {
270     return CollectionUtil.toArray(types.values(), TypeDescriptor.class);
271   }
272 
273   @Override
274   public TypeDescriptor getTypeDescriptor(String typeName) {
275     if (PrimitiveType.getInstance(typeName) != null) {
276       return null;
277     }
278     ComplexTypeDescriptor type = types.get(typeName);
279     if (type == null) {
280       type = new ComplexTypeDescriptor(typeName, this);
281       types.put(typeName, type);
282     }
283     return type;
284   }
285 
286 
287   // private helpers --------------------------------------------------------------------------------------------------------------
288 
289   /**
290    * Save.
291    */
292   public void save() {
293     try {
294       String encoding = normalizeEncoding(document.getInputEncoding());
295       File uri = new File(resolveUri(outputUri));
296       XMLUtil.saveDocument(document, uri, encoding);
297     } catch (FileNotFoundException e) {
298       throw new RuntimeException("Error writing DOMTree to " + outputUri,
299           e);
300     }
301   }
302 
303   private void beInitialized() {
304     if (this.document == null) {
305       init();
306     }
307   }
308 
309   private void init() {
310     try {
311       this.document =
312           XMLUtil.parse(resolveUri(inputUri), namespaceAware, null,
313               null, null);
314       System.out.println(
315           this.document.getDocumentElement().getNamespaceURI());
316     } catch (IOException e) {
317       throw new RuntimeException("Error parsing " + inputUri, e);
318     }
319   }
320 
321   private String resolveUri(String uri) {
322     return (context instanceof BeneratorContext ?
323         ((BeneratorContext) context).resolveRelativeUri(uri) : uri);
324   }
325 
326   @Override
327   public String toString() {
328     return getClass().getSimpleName() + "[" + inputUri +
329         (NullSafeComparator.equals(inputUri, outputUri) ? "" :
330             " -> " + outputUri) + "]";
331   }
332 
333 }