Use image data when recreating databases for testing.
[online-glom:gwt-glom.git] / src / main / java / org / glom / web / server / libglom / Document.java
1 /*
2  * Copyright (C) 2012 Openismus GmbH
3  *
4  * This file is part of GWT-Glom.
5  *
6  * GWT-Glom is free software: you can redistribute it and/or modify it
7  * under the terms of the GNU Lesser General Public License as published by the
8  * Free Software Foundation, either version 3 of the License, or (at your
9  * option) any later version.
10  *
11  * GWT-Glom is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
14  * for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with GWT-Glom.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 package org.glom.web.server.libglom;
21
22 //import java.io.ByteArrayInputStream;
23 import java.io.File;
24 import java.io.IOException;
25 //import java.io.InputStream;
26 //import java.net.URLConnection;
27 import java.text.DateFormat;
28 import java.text.NumberFormat;
29 import java.text.ParseException;
30 import java.util.ArrayList;
31 import java.util.Date;
32 import java.util.HashMap;
33 import java.util.Hashtable;
34 import java.util.List;
35 import java.util.Locale;
36 import java.util.Map;
37 import java.util.Map.Entry;
38
39 import javax.xml.parsers.DocumentBuilder;
40 import javax.xml.parsers.DocumentBuilderFactory;
41 import javax.xml.parsers.ParserConfigurationException;
42 import javax.xml.transform.OutputKeys;
43 import javax.xml.transform.Transformer;
44 import javax.xml.transform.TransformerConfigurationException;
45 import javax.xml.transform.TransformerException;
46 import javax.xml.transform.TransformerFactory;
47 import javax.xml.transform.dom.DOMSource;
48 import javax.xml.transform.stream.StreamResult;
49
50 import org.apache.commons.lang3.StringUtils;
51 import org.glom.web.server.Utils;
52 import org.glom.web.shared.DataItem;
53 import org.glom.web.shared.libglom.CustomTitle;
54 import org.glom.web.shared.libglom.Field;
55 import org.glom.web.shared.libglom.Field.GlomFieldType;
56 import org.glom.web.shared.libglom.NumericFormat;
57 import org.glom.web.shared.libglom.Relationship;
58 import org.glom.web.shared.libglom.Report;
59 import org.glom.web.shared.libglom.Translatable;
60 import org.glom.web.shared.libglom.layout.Formatting;
61 import org.glom.web.shared.libglom.layout.LayoutGroup;
62 import org.glom.web.shared.libglom.layout.LayoutItem;
63 import org.glom.web.shared.libglom.layout.LayoutItemField;
64 import org.glom.web.shared.libglom.layout.LayoutItemNotebook;
65 import org.glom.web.shared.libglom.layout.LayoutItemPortal;
66 import org.glom.web.shared.libglom.layout.LayoutItemPortal.NavigationType;
67 import org.glom.web.shared.libglom.layout.TableToViewDetails;
68 import org.glom.web.shared.libglom.layout.UsesRelationship;
69 import org.glom.web.shared.libglom.layout.UsesRelationshipImpl;
70 import org.glom.web.shared.libglom.layout.reportparts.LayoutItemGroupBy;
71 import org.jfree.util.Log;
72 import org.w3c.dom.Element;
73 import org.w3c.dom.Node;
74 import org.w3c.dom.NodeList;
75 import org.xml.sax.SAXException;
76
77 import com.google.common.io.Files;
78
79 /**
80  * @author Murray Cumming <murrayc@openismus.com>
81  * 
82  */
83 public class Document {
84
85         @SuppressWarnings("serial")
86         private static class TableInfo extends Translatable {
87                 private boolean isDefault;
88                 private boolean isHidden;
89
90                 private final Hashtable<String, Field> fieldsMap = new Hashtable<String, Field>();
91                 private final Hashtable<String, Relationship> relationshipsMap = new Hashtable<String, Relationship>();
92                 private final Hashtable<String, Report> reportsMap = new Hashtable<String, Report>();
93
94                 private List<LayoutGroup> layoutGroupsList = new ArrayList<LayoutGroup>();
95                 private List<LayoutGroup> layoutGroupsDetails = new ArrayList<LayoutGroup>();
96
97                 // A list of maps (field name to value).
98                 private List<Map<String, DataItem>> exampleRows = null;
99         }
100
101         private String fileURI = "";
102         private org.w3c.dom.Document xmlDocument = null;
103
104         private final Translatable databaseTitle = new Translatable();
105         private String translationOriginalLocale = "";
106         private final List<String> translationAvailableLocales = new ArrayList<String>();
107         private boolean isExample = false;
108         private HostingMode hostingMode = HostingMode.HOSTING_MODE_POSTGRES_CENTRAL;
109         private String connectionServer = "";
110         private String connectionDatabase = "";
111         private int connectionPort = 0;
112         private final Hashtable<String, TableInfo> tablesMap = new Hashtable<String, TableInfo>();
113
114         private static final String NODE_ROOT = "glom_document";
115         private static final String ATTRIBUTE_IS_EXAMPLE = "is_example";
116         private static final String ATTRIBUTE_TRANSLATION_ORIGINAL_LOCALE = "translation_original_locale";
117         private static final String NODE_CONNECTION = "connection";
118         private static final String ATTRIBUTE_CONNECTION_HOSTING_MODE = "hosting_mode";
119         private static final String ATTRIBUTE_CONNECTION_SERVER = "server";
120         private static final String ATTRIBUTE_CONNECTION_DATABASE = "database";
121         private static final String ATTRIBUTE_CONNECTION_PORT = "port";
122         private static final String NODE_TABLE = "table";
123         private static final String ATTRIBUTE_NAME = "name";
124         private static final String ATTRIBUTE_TITLE = "title";
125         private static final String DEPRECATED_ATTRIBUTE_DATABASE_TITLE = "database_title";
126         private static final String ATTRIBUTE_DEFAULT = "default";
127         private static final String ATTRIBUTE_HIDDEN = "hidden";
128         private static final String NODE_TRANSLATIONS_SET = "trans_set";
129         private static final String NODE_TRANSLATIONS = "trans";
130         private static final String ATTRIBUTE_TRANSLATION_LOCALE = "loc";
131         private static final String ATTRIBUTE_TRANSLATION_TITLE = "val";
132         private static final String NODE_REPORTS = "reports";
133         private static final String NODE_REPORT = "report";
134         private static final String NODE_FIELDS = "fields";
135         private static final String NODE_FIELD = "field";
136         private static final String NODE_EXAMPLE_ROWS = "example_rows";
137         private static final String NODE_EXAMPLE_ROW = "example_row";
138         private static final String NODE_VALUE = "value";
139         private static final String ATTRIBUTE_COLUMN = "column";
140         private static final String ATTRIBUTE_PRIMARY_KEY = "primary_key";
141         private static final String ATTRIBUTE_FIELD_TYPE = "type";
142         private static final String NODE_FORMATTING = "formatting";
143         // private static final String ATTRIBUTE_TEXT_FORMAT_MULTILINE = "format_text_multiline";
144         private static final String ATTRIBUTE_USE_THOUSANDS_SEPARATOR = "format_thousands_separator";
145         private static final String ATTRIBUTE_DECIMAL_PLACES = "format_decimal_places";
146         private static final String NODE_RELATIONSHIPS = "relationships";
147         private static final String NODE_RELATIONSHIP = "relationship";
148         private static final String ATTRIBUTE_RELATIONSHIP_FROM_FIELD = "key";
149         private static final String ATTRIBUTE_RELATIONSHIP_TO_TABLE = "other_table";
150         private static final String ATTRIBUTE_RELATIONSHIP_TO_FIELD = "other_key";
151         private static final String NODE_DATA_LAYOUTS = "data_layouts";
152         private static final String NODE_DATA_LAYOUT = "data_layout";
153         private static final String NODE_DATA_LAYOUT_GROUPS = "data_layout_groups";
154         private static final String NODE_DATA_LAYOUT_GROUP = "data_layout_group";
155         private static final String ATTRIBUTE_LAYOUT_GROUP_COLUMNS_COUNT = "columns_count";
156         private static final String NODE_DATA_LAYOUT_NOTEBOOK = "data_layout_notebook";
157         private static final String NODE_DATA_LAYOUT_PORTAL = "data_layout_portal";
158         private static final String NODE_DATA_LAYOUT_PORTAL_NAVIGATIONRELATIONSHIP = "portal_navigation_relationship";
159         private static final String ATTRIBUTE_PORTAL_NAVIGATION_TYPE = "navigation_type";
160         private static final String ATTRIBUTE_PORTAL_NAVIGATION_TYPE_AUTOMATIC = "automatic";
161         private static final String ATTRIBUTE_PORTAL_NAVIGATION_TYPE_SPECIFIC = "specific";
162         private static final String ATTRIBUTE_PORTAL_NAVIGATION_TYPE_NONE = "none";
163         private static final String ATTRIBUTE_RELATIONSHIP_NAME = "relationship";
164         private static final String ATTRIBUTE_RELATED_RELATIONSHIP_NAME = "related_relationship";
165         private static final String NODE_DATA_LAYOUT_ITEM = "data_layout_item";
166         private static final String NODE_CUSTOM_TITLE = "title_custom";
167         private static final String ATTRIBUTE_CUSTOM_TITLE_USE_CUSTOM = "use_custom";
168         private static final String NODE_DATA_LAYOUT_ITEM_GROUPBY = "data_layout_item_groupby";
169         private static final String NODE_GROUPBY = "groupby";
170         private static final String NODE_SECONDARY_FIELDS = "secondary_fields";
171         private static final String ATTRIBUTE_USE_DEFAULT_FORMATTING = "use_default_formatting";
172         private static final String LAYOUT_NAME_DETAILS = "details";
173         private static final String LAYOUT_NAME_LIST = "list";
174         private static final String QUOTE_FOR_FILE_FORMAT = "\"";
175
176         public void setFileURI(final String fileURI) {
177                 this.fileURI = fileURI;
178         }
179
180         public String getFileURI() {
181                 return fileURI;
182         }
183
184         // TODO: Make sure these have the correct values.
185         public enum LoadFailureCodes {
186                 LOAD_FAILURE_CODE_NONE, LOAD_FAILURE_CODE_NOT_FOUND, LOAD_FAILURE_CODE_FILE_VERSION_TOO_NEW
187         };
188
189         public boolean load() {
190                 final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
191                 DocumentBuilder documentBuilder = null;
192                 try {
193                         documentBuilder = dbf.newDocumentBuilder();
194                 } catch (final ParserConfigurationException e) {
195                         // TODO Auto-generated catch block
196                         e.printStackTrace();
197                         return false;
198                 }
199
200                 try {
201                         xmlDocument = documentBuilder.parse(fileURI);
202                 } catch (final SAXException e) {
203                         // TODO Auto-generated catch block
204                         e.printStackTrace();
205                         return false;
206                 } catch (final IOException e) {
207                         // TODO Auto-generated catch block
208                         e.printStackTrace();
209                         return false;
210                 }
211
212                 final Element rootNode = xmlDocument.getDocumentElement();
213                 if (rootNode.getNodeName() != NODE_ROOT) {
214                         Log.error("Unexpected XML root node name found: " + rootNode.getNodeName());
215                         return false;
216                 }
217
218                 //Get the database title, falling back to the deprecated XML format for it:
219                 //TODO: load() show complain (via an enum result) if the document format version is less than 7.
220                 final String databaseTitleStr = rootNode.getAttribute(ATTRIBUTE_TITLE);
221                 final String deprecatedDatabaseTitleStr = rootNode.getAttribute(DEPRECATED_ATTRIBUTE_DATABASE_TITLE);
222                 if(!StringUtils.isEmpty(databaseTitleStr)) {
223                         databaseTitle.setTitleOriginal(databaseTitleStr);
224                 } else {
225                         databaseTitle.setTitleOriginal(deprecatedDatabaseTitleStr);
226                 }
227                 loadTitle(rootNode, databaseTitle);
228
229                 translationOriginalLocale = rootNode.getAttribute(ATTRIBUTE_TRANSLATION_ORIGINAL_LOCALE);
230                 translationAvailableLocales.add(translationOriginalLocale); // Just a cache.
231
232                 isExample = getAttributeAsBoolean(rootNode, ATTRIBUTE_IS_EXAMPLE);
233
234                 final Element nodeConnection = getElementByName(rootNode, NODE_CONNECTION);
235                 if (nodeConnection != null) {
236                         final String strHostingMode = nodeConnection.getAttribute(ATTRIBUTE_CONNECTION_HOSTING_MODE);
237                         if (strHostingMode == "postgres_central") {
238                                 hostingMode = HostingMode.HOSTING_MODE_POSTGRES_CENTRAL;
239                         } else if (strHostingMode == "sqlite") {
240                                 hostingMode = HostingMode.HOSTING_MODE_SQLITE;
241                         } else {
242                                 hostingMode = HostingMode.HOSTING_MODE_POSTGRES_SELF;
243                         }
244
245                         connectionServer = nodeConnection.getAttribute(ATTRIBUTE_CONNECTION_SERVER);
246                         connectionDatabase = nodeConnection.getAttribute(ATTRIBUTE_CONNECTION_DATABASE);
247                         connectionPort = (int) getAttributeAsDecimal(nodeConnection, ATTRIBUTE_CONNECTION_PORT);
248                 }
249
250                 // We first load the fields, relationships, etc,
251                 // for all tables:
252                 final List<Node> listTableNodes = getChildrenByTagName(rootNode, NODE_TABLE);
253                 for (final Node node : listTableNodes) {
254                         if (!(node instanceof Element)) {
255                                 continue;
256                         }
257
258                         final Element element = (Element) node;
259                         final TableInfo info = loadTableNodeBasic(element);
260                         tablesMap.put(info.getName(), info);
261                 }
262
263                 // We then load the layouts for all tables, because they
264                 // need the fields and relationships for all tables:
265                 for (final Node node : listTableNodes) {
266                         if (!(node instanceof Element)) {
267                                 continue;
268                         }
269
270                         final Element element = (Element) node;
271                         final String tableName = element.getAttribute(ATTRIBUTE_NAME);
272
273                         // We first load the fields, relationships, etc:
274                         final TableInfo info = getTableInfo(tableName);
275                         if (info == null) {
276                                 continue;
277                         }
278
279                         // We then load the layouts afterwards, because they
280                         // need the fields and relationships:
281                         loadTableLayouts(element, info);
282
283                         tablesMap.put(info.getName(), info);
284                 }
285
286                 return true;
287         };
288
289         private Element getElementByName(final Element parentElement, final String tagName) {
290                 final List<Node> listNodes = getChildrenByTagName(parentElement, tagName);
291                 if (listNodes == null) {
292                         return null;
293                 }
294
295                 if (listNodes.size() == 0) {
296                         return null;
297                 }
298
299                 return (Element) listNodes.get(0);
300         }
301
302         private boolean getAttributeAsBoolean(final Element node, final String attributeName) {
303                 final String str = node.getAttribute(attributeName);
304                 if (str == null) {
305                         return false;
306                 }
307
308                 return str.equals("true");
309         }
310
311         private void setAttributeAsBoolean(final Element node, final String attributeName, final boolean value) {
312                 final String str = value ? "true" : "false";
313                 node.setAttribute(attributeName, str);
314         }
315
316         private double getAttributeAsDecimal(final Element node, final String attributeName) {
317                 final String str = node.getAttribute(attributeName);
318                 if (StringUtils.isEmpty(str)) {
319                         return 0;
320                 }
321
322                 double value = 0;
323                 try {
324                         value = Double.valueOf(str);
325                 } catch (final NumberFormatException e) {
326                         // e.printStackTrace();
327                 }
328
329                 return value;
330         }
331
332         private String getStringForDecimal(final double value) {
333                 final NumberFormat format = NumberFormat.getInstance(Locale.US);
334                 format.setGroupingUsed(false); // TODO: Does this change it system-wide?
335                 return format.format(value);
336         }
337
338         private void setAttributeAsDecimal(final Element node, final String attributeName, final double value) {
339                 node.setAttribute(attributeName, getStringForDecimal(value));
340         }
341
342         /**
343          * Load a title and its translations.
344          * 
345          * @param node
346          *            The XML Element that may contain a title attribute and a trans_set of translations of the title.
347          * @param title
348          */
349         private void loadTitle(final Element node, final Translatable title) {
350                 title.setName(node.getAttribute(ATTRIBUTE_NAME));
351
352                 title.setTitleOriginal(node.getAttribute(ATTRIBUTE_TITLE));
353
354                 final Element nodeSet = getElementByName(node, NODE_TRANSLATIONS_SET);
355                 if (nodeSet == null) {
356                         return;
357                 }
358
359                 final List<Node> listNodes = getChildrenByTagName(nodeSet, NODE_TRANSLATIONS);
360                 if (listNodes == null) {
361                         return;
362                 }
363
364                 for (final Node transNode : listNodes) {
365                         if (!(transNode instanceof Element)) {
366                                 continue;
367                         }
368
369                         final Element element = (Element) transNode;
370
371                         final String locale = element.getAttribute(ATTRIBUTE_TRANSLATION_LOCALE);
372                         final String translatedTitle = element.getAttribute(ATTRIBUTE_TRANSLATION_TITLE);
373                         if (!StringUtils.isEmpty(locale) && !StringUtils.isEmpty(translatedTitle)) {
374                                 title.setTitle(translatedTitle, locale);
375
376                                 // Remember any new translation locales in our cached list:
377                                 if (!translationAvailableLocales.contains(locale)) {
378                                         translationAvailableLocales.add(locale);
379                                 }
380                         }
381                 }
382         }
383
384         private void saveTitle(final org.w3c.dom.Document doc, final Element node, final Translatable title) {
385                 node.setAttribute(ATTRIBUTE_NAME, title.getName());
386
387                 node.setAttribute(ATTRIBUTE_TITLE, title.getTitleOriginal());
388
389                 final Element nodeSet = createElement(doc, node, NODE_TRANSLATIONS_SET);
390
391                 for (final Entry<String, String> entry : title.getTranslationsMap().entrySet()) {
392                         final Element element = createElement(doc, nodeSet, NODE_TRANSLATIONS);
393
394                         element.setAttribute(ATTRIBUTE_TRANSLATION_LOCALE, entry.getKey());
395                         element.setAttribute(ATTRIBUTE_TRANSLATION_TITLE, entry.getValue());
396                 }
397         }
398
399         /**
400          * @param tableNode
401          * @return
402          */
403         private TableInfo loadTableNodeBasic(final Element tableNode) {
404                 final TableInfo info = new TableInfo();
405                 loadTitle(tableNode, info);
406                 final String tableName = info.getName();
407
408                 info.isDefault = getAttributeAsBoolean(tableNode, ATTRIBUTE_DEFAULT);
409                 info.isHidden = getAttributeAsBoolean(tableNode, ATTRIBUTE_HIDDEN);
410
411                 // These should be loaded before the fields, because the fields use them.
412                 final Element relationshipsNode = getElementByName(tableNode, NODE_RELATIONSHIPS);
413                 if (relationshipsNode != null) {
414                         final List<Node> listNodes = getChildrenByTagName(relationshipsNode, NODE_RELATIONSHIP);
415                         for (final Node node : listNodes) {
416                                 if (!(node instanceof Element)) {
417                                         continue;
418                                 }
419
420                                 final Element element = (Element) node;
421                                 final Relationship relationship = new Relationship();
422                                 loadTitle(element, relationship);
423                                 relationship.setFromTable(tableName);
424                                 relationship.setFromField(element.getAttribute(ATTRIBUTE_RELATIONSHIP_FROM_FIELD));
425                                 relationship.setToTable(element.getAttribute(ATTRIBUTE_RELATIONSHIP_TO_TABLE));
426                                 relationship.setToField(element.getAttribute(ATTRIBUTE_RELATIONSHIP_TO_FIELD));
427
428                                 info.relationshipsMap.put(relationship.getName(), relationship);
429                         }
430                 }
431
432                 final Element fieldsNode = getElementByName(tableNode, NODE_FIELDS);
433                 if (fieldsNode != null) {
434                         final List<Node> listNodes = getChildrenByTagName(fieldsNode, NODE_FIELD);
435                         for (final Node node : listNodes) {
436                                 if (!(node instanceof Element)) {
437                                         continue;
438                                 }
439
440                                 final Element element = (Element) node;
441                                 final Field field = new Field();
442                                 loadField(element, field);
443
444                                 info.fieldsMap.put(field.getName(), field);
445                         }
446                 }
447
448                 // We do not normally use this,
449                 // though we do use it during testing, in SelfHoster, to recreate the database data.
450                 final Element exampleRowsNode = getElementByName(tableNode, NODE_EXAMPLE_ROWS);
451                 if (exampleRowsNode != null) {
452
453                         final List<Map<String, DataItem>> exampleRows = new ArrayList<Map<String, DataItem>>();
454                         final List<Node> listNodes = getChildrenByTagName(exampleRowsNode, NODE_EXAMPLE_ROW);
455                         for (final Node node : listNodes) {
456                                 if (!(node instanceof Element)) {
457                                         continue;
458                                 }
459
460                                 final Element element = (Element) node;
461                                 final Map<String, DataItem> row = new HashMap<String, DataItem>();
462
463                                 final List<Node> listNodesValues = getChildrenByTagName(element, NODE_VALUE);
464                                 for (final Node nodeValue : listNodesValues) {
465                                         if (!(nodeValue instanceof Element)) {
466                                                 continue;
467                                         }
468
469                                         final Element elementValue = (Element) nodeValue;
470                                         final String fieldName = elementValue.getAttribute(ATTRIBUTE_COLUMN);
471                                         if (StringUtils.isEmpty(fieldName)) {
472                                                 continue;
473                                         }
474
475                                         DataItem value = null;
476                                         final Field field = info.fieldsMap.get(fieldName);
477                                         if (field != null) {
478                                                 value = getNodeTextChildAsValue(elementValue, field.getGlomType());
479                                         }
480                                         row.put(fieldName, value);
481                                 }
482
483                                 exampleRows.add(row);
484                         }
485
486                         info.exampleRows = exampleRows;
487                 }
488
489                 return info;
490         }
491
492         /**
493          * @param elementValue
494          * @param glomType
495          * @return
496          */
497         private DataItem getNodeTextChildAsValue(final Element element, final GlomFieldType type) {
498                 final DataItem result = new DataItem();
499
500                 final String str = element.getTextContent();
501
502                 // Unescape "" to ", because to_file_format() escaped ", as specified by the CSV RFC:
503                 String unescaped = "";
504                 if (type == GlomFieldType.TYPE_IMAGE) {
505                         unescaped = str; // binary data does not have quote characters so we do not bother to escape or unescape it.
506                 } else {
507                         unescaped = str.replace(QUOTE_FOR_FILE_FORMAT + QUOTE_FOR_FILE_FORMAT, QUOTE_FOR_FILE_FORMAT);
508                 }
509
510                 switch (type) {
511                 case TYPE_BOOLEAN: {
512                         final boolean value = (unescaped == "true");
513                         result.setBoolean(value);
514                         break;
515                 }
516                 case TYPE_DATE: {
517                         final DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, Locale.ROOT);
518                         Date value = null;
519                         try {
520                                 value = dateFormat.parse(unescaped);
521                         } catch (final ParseException e) {
522                                 // e.printStackTrace();
523                         }
524                         result.setDate(value);
525                         break;
526                 }
527                 case TYPE_IMAGE: {
528                         //Glom (at least since 2.23/24) uses base64 for the images:
529                         
530                         //Discover the mime type:
531                         final byte[] bytes = com.google.gwt.user.server.Base64Utils.fromBase64(unescaped);
532                         /*
533                         final InputStream is = new ByteArrayInputStream(bytes);
534                         String contentType = "";
535                         try {
536                                 contentType = URLConnection.guessContentTypeFromStream(is);
537                         } catch (IOException e) {
538                                 Log.error("getNodeTextChildAsValue(): unrecognised image data content type.");
539                         }       
540                         
541                         final String base64 = "data:" + contentType + ";base64," + unescaped;
542                         result.setImageDataUrl(base64);
543                         */
544                         result.setImageData(bytes);
545                         break;
546                 }
547                 case TYPE_NUMERIC: {
548                         double value = 0;
549                         try {
550                                 value = Double.valueOf(unescaped);
551                         } catch (final NumberFormatException e) {
552                                 // e.printStackTrace();
553                         }
554
555                         result.setNumber(value);
556                         break;
557                 }
558                 case TYPE_TEXT:
559                         result.setText(unescaped);
560                         break;
561                 case TYPE_TIME:
562                         // TODO
563                         break;
564                 default:
565                         Log.error("getNodeTextChildAsValue(): unexpected or invalid field type.");
566                         break;
567                 }
568
569                 return result;
570         }
571
572         private void setNodeTextChildAsValue(final Element element, final DataItem value, final GlomFieldType type) {
573                 String str = "";
574
575                 switch (type) {
576                 case TYPE_BOOLEAN: {
577                         str = value.getBoolean() ? "true" : "false";
578                         break;
579                 }
580                 case TYPE_DATE: {
581                         // TODO: This is not really the format used by the Glom document:
582                         final DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, Locale.ROOT);
583                         str = dateFormat.format(value.getDate());
584                         break;
585                 }
586                 case TYPE_IMAGE: {
587                         str = ""; // TODO
588                         break;
589                 }
590                 case TYPE_NUMERIC: {
591                         str = getStringForDecimal(value.getNumber());
592                         break;
593                 }
594                 case TYPE_TEXT:
595                         str = value.getText();
596                         break;
597                 case TYPE_TIME:
598                         str = ""; // TODO
599                         break;
600                 default:
601                         Log.error("setNodeTextChildAsValue(): unexpected or invalid field type.");
602                         break;
603                 }
604
605                 final String escaped = str.replace(QUOTE_FOR_FILE_FORMAT, QUOTE_FOR_FILE_FORMAT + QUOTE_FOR_FILE_FORMAT);
606                 element.setTextContent(escaped);
607         }
608
609         private void saveTableNodeBasic(final org.w3c.dom.Document doc, final Element tableNode, final TableInfo info) {
610                 saveTitle(doc, tableNode, info);
611
612                 setAttributeAsBoolean(tableNode, ATTRIBUTE_DEFAULT, info.isDefault);
613                 setAttributeAsBoolean(tableNode, ATTRIBUTE_HIDDEN, info.isHidden);
614
615                 final Element relationshipsNode = createElement(doc, tableNode, NODE_RELATIONSHIPS);
616                 for (final Relationship relationship : info.relationshipsMap.values()) {
617                         final Element element = createElement(doc, relationshipsNode, NODE_RELATIONSHIP);
618                         saveTitle(doc, element, relationship);
619
620                         element.setAttribute(ATTRIBUTE_RELATIONSHIP_FROM_FIELD, relationship.getFromField());
621                         element.setAttribute(ATTRIBUTE_RELATIONSHIP_TO_TABLE, relationship.getToTable());
622                         element.setAttribute(ATTRIBUTE_RELATIONSHIP_TO_FIELD, relationship.getToField());
623                 }
624
625                 final Element fieldsNode = createElement(doc, tableNode, NODE_FIELDS);
626                 for (final Field field : info.fieldsMap.values()) {
627                         final Element element = createElement(doc, fieldsNode, NODE_FIELD);
628                         saveField(doc, element, field);
629                 }
630
631                 final Element exampleRowsNode = createElement(doc, tableNode, NODE_EXAMPLE_ROWS);
632
633                 for (final Map<String, DataItem> row : info.exampleRows) {
634                         final Element node = createElement(doc, exampleRowsNode, NODE_EXAMPLE_ROW);
635
636                         // TODO: This assumes that fieldsMap.values() will have the same sequence as the values,
637                         final int i = 0;
638                         for (final Field field : info.fieldsMap.values()) {
639                                 if (i < row.size()) {
640                                         break;
641                                 }
642
643                                 final String fieldName = field.getName();
644                                 if (StringUtils.isEmpty(fieldName)) {
645                                         continue;
646                                 }
647
648                                 final DataItem dataItem = row.get(fieldName);
649                                 if (dataItem == null) {
650                                         continue;
651                                 }
652
653                                 final Element elementValue = createElement(doc, node, NODE_VALUE);
654                                 elementValue.setAttribute(ATTRIBUTE_COLUMN, fieldName);
655                                 setNodeTextChildAsValue(elementValue, dataItem, field.getGlomType());
656                         }
657                 }
658         }
659
660         /**
661          * @param doc
662          * @param element
663          * @param field
664          */
665         private void saveField(final org.w3c.dom.Document doc, final Element element, final Field field) {
666                 saveTitle(doc, element, field);
667
668                 String fieldTypeStr = "";
669
670                 switch (field.getGlomType()) {
671                 case TYPE_BOOLEAN:
672                         fieldTypeStr = "Boolean";
673                         break;
674                 case TYPE_DATE:
675                         fieldTypeStr = "Date";
676                         break;
677                 case TYPE_IMAGE:
678                         fieldTypeStr = "Image";
679                         break;
680                 case TYPE_NUMERIC:
681                         fieldTypeStr = "Number";
682                         break;
683                 case TYPE_TEXT:
684                         fieldTypeStr = "Text";
685                         break;
686                 case TYPE_TIME:
687                         fieldTypeStr = "Time";
688                         break;
689                 default:
690                         break;
691                 }
692                 element.setAttribute(ATTRIBUTE_FIELD_TYPE, fieldTypeStr);
693
694                 setAttributeAsBoolean(element, ATTRIBUTE_PRIMARY_KEY, field.getPrimaryKey());
695
696                 final Element elementFormatting = createElement(doc, element, NODE_FORMATTING);
697                 saveFormatting(elementFormatting, field.getFormatting());
698         }
699
700         /**
701          * @param elementFormatting
702          * @param formatting
703          */
704         private void saveFormatting(final Element element, final Formatting formatting) {
705                 // formatting.setTextFormatMultiline(getAttributeAsBoolean(elementFormatting, ATTRIBUTE_TEXT_FORMAT_MULTILINE));
706
707                 final NumericFormat numericFormatting = formatting.getNumericFormat();
708                 if (numericFormatting != null) {
709                         setAttributeAsBoolean(element, ATTRIBUTE_USE_THOUSANDS_SEPARATOR,
710                                         numericFormatting.getUseThousandsSeparator());
711                         setAttributeAsDecimal(element, ATTRIBUTE_DECIMAL_PLACES, numericFormatting.getDecimalPlaces());
712                 }
713         }
714
715         /**
716          * @param tableNode
717          * @param info
718          */
719         private void loadTableLayouts(final Element tableNode, final TableInfo info) {
720                 final String tableName = info.getName();
721
722                 final Element layoutsNode = getElementByName(tableNode, NODE_DATA_LAYOUTS);
723                 if (layoutsNode != null) {
724                         final List<Node> listNodes = getChildrenByTagName(layoutsNode, NODE_DATA_LAYOUT);
725                         for (final Node node : listNodes) {
726                                 if (!(node instanceof Element)) {
727                                         continue;
728                                 }
729
730                                 final Element element = (Element) node;
731                                 final String name = element.getAttribute(ATTRIBUTE_NAME);
732                                 final List<LayoutGroup> listLayoutGroups = loadLayoutNode(element, tableName);
733                                 if (name.equals(LAYOUT_NAME_DETAILS)) {
734                                         info.layoutGroupsDetails = listLayoutGroups;
735                                 } else if (name.equals(LAYOUT_NAME_LIST)) {
736                                         info.layoutGroupsList = listLayoutGroups;
737                                 } else {
738                                         Log.error("loadTableNode(): unexpected layout name: " + name);
739                                 }
740                         }
741                 }
742
743                 final Element reportsNode = getElementByName(tableNode, NODE_REPORTS);
744                 if (reportsNode != null) {
745                         final List<Node> listNodes = getChildrenByTagName(reportsNode, NODE_REPORT);
746                         for (final Node node : listNodes) {
747                                 if (!(node instanceof Element)) {
748                                         continue;
749                                 }
750
751                                 final Element element = (Element) node;
752                                 final Report report = new Report();
753                                 loadReport(element, report, tableName);
754
755                                 info.reportsMap.put(report.getName(), report);
756                         }
757                 }
758         }
759
760         /**
761          * @param node
762          * @return
763          */
764         private List<LayoutGroup> loadLayoutNode(final Element node, final String tableName) {
765                 if (node == null) {
766                         return null;
767                 }
768
769                 final List<LayoutGroup> result = new ArrayList<LayoutGroup>();
770
771                 final List<Node> listNodes = getChildrenByTagName(node, NODE_DATA_LAYOUT_GROUPS);
772                 for (final Node nodeGroups : listNodes) {
773                         if (!(nodeGroups instanceof Element)) {
774                                 continue;
775                         }
776
777                         final Element elementGroups = (Element) nodeGroups;
778
779                         final NodeList list = elementGroups.getChildNodes();
780                         final int num = list.getLength();
781                         for (int i = 0; i < num; i++) {
782                                 final Node nodeLayoutGroup = list.item(i);
783                                 if (nodeLayoutGroup == null) {
784                                         continue;
785                                 }
786
787                                 if (!(nodeLayoutGroup instanceof Element)) {
788                                         continue;
789                                 }
790
791                                 final Element element = (Element) nodeLayoutGroup;
792                                 final String tagName = element.getTagName();
793                                 if (tagName == NODE_DATA_LAYOUT_GROUP) {
794                                         final LayoutGroup group = new LayoutGroup();
795                                         loadDataLayoutGroup(element, group, tableName);
796                                         result.add(group);
797                                 } else if (tagName == NODE_DATA_LAYOUT_NOTEBOOK) {
798                                         final LayoutItemNotebook group = new LayoutItemNotebook();
799                                         loadDataLayoutGroup(element, group, tableName);
800                                         result.add(group);
801                                 } else if (tagName == NODE_DATA_LAYOUT_PORTAL) {
802                                         final LayoutItemPortal portal = new LayoutItemPortal();
803                                         loadDataLayoutPortal(element, portal, tableName);
804                                         result.add(portal);
805                                 }
806                         }
807                 }
808
809                 return result;
810         }
811
812         /**
813          * @param element
814          * @param tableName
815          * @param portal
816          */
817         private void loadUsesRelationship(final Element element, final String tableName, final UsesRelationship item) {
818                 if (element == null) {
819                         return;
820                 }
821
822                 if (item == null) {
823                         return;
824                 }
825
826                 final String relationshipName = element.getAttribute(ATTRIBUTE_RELATIONSHIP_NAME);
827                 Relationship relationship = null;
828                 if (!StringUtils.isEmpty(relationshipName)) {
829                         // std::cout << "  debug in : tableName=" << tableName << ", relationshipName=" << relationship_name <<
830                         // std::endl;
831                         relationship = getRelationship(tableName, relationshipName);
832                         item.setRelationship(relationship);
833
834                         if (relationship == null) {
835                                 Log.error("relationship not found: " + relationshipName + ", in table: " + tableName);
836                         }
837                 }
838
839                 // TODO: Unit test loading of doubly-related fields.
840                 final String relatedRelationshipName = element.getAttribute(ATTRIBUTE_RELATED_RELATIONSHIP_NAME);
841                 if (!StringUtils.isEmpty(relatedRelationshipName) && (relationship != null)) {
842                         final Relationship relatedRelationship = getRelationship(relationship.getToTable(), relatedRelationshipName);
843                         item.setRelatedRelationship(relatedRelationship);
844
845                         if (relatedRelationship == null) {
846                                 Log.error("related relationship not found in table=" + relationship.getToTable() + ",  name="
847                                                 + relatedRelationshipName);
848                         }
849                 }
850         }
851
852         /**
853          * getElementsByTagName() is recursive, but we do not want that.
854          * 
855          * @param node
856          * @param
857          * @return
858          */
859         private List<Node> getChildrenByTagName(final Element parentNode, final String tagName) {
860                 final List<Node> result = new ArrayList<Node>();
861
862                 final NodeList list = parentNode.getElementsByTagName(tagName);
863                 final int num = list.getLength();
864                 for (int i = 0; i < num; i++) {
865                         final Node node = list.item(i);
866                         if (node == null) {
867                                 continue;
868                         }
869
870                         final Node itemParentNode = node.getParentNode();
871                         if (itemParentNode.equals(parentNode)) {
872                                 result.add(node);
873                         }
874                 }
875
876                 return result;
877         }
878
879         /**
880          * @param element
881          * @param group
882          */
883         private void loadDataLayoutGroup(final Element nodeGroup, final LayoutGroup group, final String tableName) {
884                 loadTitle(nodeGroup, group);
885
886                 // Read the column count:
887                 int columnCount = (int) getAttributeAsDecimal(nodeGroup, ATTRIBUTE_LAYOUT_GROUP_COLUMNS_COUNT);
888                 if (columnCount < 1) {
889                         columnCount = 1; // 0 is a useless default.
890                 }
891                 group.setColumnCount(columnCount);
892
893                 // Get the child items:
894                 final NodeList listNodes = nodeGroup.getChildNodes();
895                 final int num = listNodes.getLength();
896                 for (int i = 0; i < num; i++) {
897                         final Node node = listNodes.item(i);
898                         if (!(node instanceof Element)) {
899                                 continue;
900                         }
901
902                         final Element element = (Element) node;
903                         final String tagName = element.getTagName();
904                         if (tagName == NODE_DATA_LAYOUT_GROUP) {
905                                 final LayoutGroup childGroup = new LayoutGroup();
906                                 loadDataLayoutGroup(element, childGroup, tableName);
907                                 group.addItem(childGroup);
908                         } else if (tagName == NODE_DATA_LAYOUT_NOTEBOOK) {
909                                 final LayoutItemNotebook childGroup = new LayoutItemNotebook();
910                                 loadDataLayoutGroup(element, childGroup, tableName);
911                                 group.addItem(childGroup);
912                         } else if (tagName == NODE_DATA_LAYOUT_PORTAL) {
913                                 final LayoutItemPortal childGroup = new LayoutItemPortal();
914                                 loadDataLayoutPortal(element, childGroup, tableName);
915                                 group.addItem(childGroup);
916                         } else if (element.getTagName() == NODE_DATA_LAYOUT_ITEM) {
917                                 final LayoutItemField item = new LayoutItemField();
918                                 loadDataLayoutItemField(element, item, tableName);
919                                 group.addItem(item);
920                         } else if (element.getTagName() == NODE_DATA_LAYOUT_ITEM_GROUPBY) {
921                                 final LayoutItemGroupBy item = new LayoutItemGroupBy();
922                                 loadDataLayoutItemGroupBy(element, item, tableName);
923                                 group.addItem(item);
924                         }
925                 }
926         }
927
928         /**
929          * @param element
930          * @param item
931          * @param tableName
932          */
933         private void loadDataLayoutItemGroupBy(final Element element, final LayoutItemGroupBy item, final String tableName) {
934                 loadDataLayoutGroup(element, item, tableName);
935
936                 final Element elementGroupBy = getElementByName(element, NODE_GROUPBY);
937                 if (elementGroupBy == null) {
938                         return;
939                 }
940
941                 final LayoutItemField fieldGroupBy = new LayoutItemField();
942                 loadDataLayoutItemField(elementGroupBy, fieldGroupBy, tableName);
943                 item.setFieldGroupBy(fieldGroupBy);
944
945                 final Element elementSecondaryFields = getElementByName(element, NODE_SECONDARY_FIELDS);
946                 if (elementSecondaryFields == null) {
947                         return;
948                 }
949
950                 final Element elementLayoutGroup = getElementByName(elementSecondaryFields, NODE_DATA_LAYOUT_GROUP);
951                 if (elementLayoutGroup != null) {
952                         final LayoutGroup secondaryLayoutGroup = new LayoutGroup();
953                         loadDataLayoutGroup(elementLayoutGroup, secondaryLayoutGroup, tableName);
954                         item.setSecondaryFields(secondaryLayoutGroup);
955                 }
956         }
957
958         /**
959          * @param element
960          * @param item
961          */
962         private void loadDataLayoutItemField(final Element element, final LayoutItemField item, final String tableName) {
963                 item.setName(element.getAttribute(ATTRIBUTE_NAME));
964                 loadUsesRelationship(element, tableName, item);
965
966                 final Element elementCustomTitle = getElementByName(element, NODE_CUSTOM_TITLE);
967                 if (elementCustomTitle != null) {
968                         final CustomTitle customTitle = item.getCustomTitle();
969                         customTitle.setUseCustomTitle(getAttributeAsBoolean(elementCustomTitle, ATTRIBUTE_CUSTOM_TITLE_USE_CUSTOM));
970                         loadTitle(elementCustomTitle, customTitle); // LayoutItemField doesn't use its own title member.
971                 }
972
973                 // Get the actual field:
974                 final String fieldName = item.getName();
975                 final String inTableName = item.getTableUsed(tableName);
976                 final Field field = getField(inTableName, fieldName);
977                 item.setFullFieldDetails(field);
978
979                 item.setUseDefaultFormatting(getAttributeAsBoolean(element, ATTRIBUTE_USE_DEFAULT_FORMATTING));
980
981                 final Element elementFormatting = getElementByName(element, NODE_FORMATTING);
982                 if (elementFormatting != null) {
983                         loadFormatting(elementFormatting, item.getFormatting());
984                 }
985         }
986
987         /**
988          * @param element
989          * @param childGroup
990          */
991         private void loadDataLayoutPortal(final Element element, final LayoutItemPortal portal, final String tableName) {
992                 loadUsesRelationship(element, tableName, portal);
993                 final String relatedTableName = portal.getTableUsed(tableName);
994                 loadDataLayoutGroup(element, portal, relatedTableName);
995
996                 final Element elementNavigation = getElementByName(element, NODE_DATA_LAYOUT_PORTAL_NAVIGATIONRELATIONSHIP);
997                 if (elementNavigation != null) {
998                         final String navigationTypeAsString = elementNavigation.getAttribute(ATTRIBUTE_PORTAL_NAVIGATION_TYPE);
999                         if (StringUtils.isEmpty(navigationTypeAsString)
1000                                         || navigationTypeAsString == ATTRIBUTE_PORTAL_NAVIGATION_TYPE_AUTOMATIC) {
1001                                 portal.setNavigationType(LayoutItemPortal.NavigationType.NAVIGATION_AUTOMATIC);
1002                         } else if (navigationTypeAsString == ATTRIBUTE_PORTAL_NAVIGATION_TYPE_NONE) {
1003                                 portal.setNavigationType(LayoutItemPortal.NavigationType.NAVIGATION_NONE);
1004                         } else if (navigationTypeAsString == ATTRIBUTE_PORTAL_NAVIGATION_TYPE_SPECIFIC) {
1005                                 // Read the specified relationship name:
1006                                 final UsesRelationship relationshipNavigationSpecific = new UsesRelationshipImpl();
1007                                 loadUsesRelationship(elementNavigation, relatedTableName, relationshipNavigationSpecific);
1008                                 portal.setNavigationRelationshipSpecific(relationshipNavigationSpecific);
1009                         }
1010                 }
1011
1012         }
1013
1014         /**
1015          * @param element
1016          * @param field
1017          */
1018         private void loadField(final Element element, final Field field) {
1019                 loadTitle(element, field);
1020
1021                 Field.GlomFieldType fieldType = Field.GlomFieldType.TYPE_INVALID;
1022                 final String fieldTypeStr = element.getAttribute(ATTRIBUTE_FIELD_TYPE);
1023                 if (!StringUtils.isEmpty(fieldTypeStr)) {
1024                         if (fieldTypeStr.equals("Boolean")) {
1025                                 fieldType = Field.GlomFieldType.TYPE_BOOLEAN;
1026                         } else if (fieldTypeStr.equals("Date")) {
1027                                 fieldType = Field.GlomFieldType.TYPE_DATE;
1028                         } else if (fieldTypeStr.equals("Image")) {
1029                                 fieldType = Field.GlomFieldType.TYPE_IMAGE;
1030                         } else if (fieldTypeStr.equals("Number")) {
1031                                 fieldType = Field.GlomFieldType.TYPE_NUMERIC;
1032                         } else if (fieldTypeStr.equals("Text")) {
1033                                 fieldType = Field.GlomFieldType.TYPE_TEXT;
1034                         } else if (fieldTypeStr.equals("Time")) {
1035                                 fieldType = Field.GlomFieldType.TYPE_TIME;
1036                         }
1037                 }
1038
1039                 field.setGlomFieldType(fieldType);
1040
1041                 field.setPrimaryKey(getAttributeAsBoolean(element, ATTRIBUTE_PRIMARY_KEY));
1042
1043                 final Element elementFormatting = getElementByName(element, NODE_FORMATTING);
1044                 if (elementFormatting != null) {
1045                         loadFormatting(elementFormatting, field.getFormatting());
1046                 }
1047         }
1048
1049         /**
1050          * @param elementFormatting
1051          * @param formatting
1052          */
1053         private void loadFormatting(final Element elementFormatting, final Formatting formatting) {
1054                 if (elementFormatting == null) {
1055                         return;
1056                 }
1057
1058                 if (formatting == null) {
1059                         return;
1060                 }
1061
1062                 // formatting.setTextFormatMultiline(getAttributeAsBoolean(elementFormatting, ATTRIBUTE_TEXT_FORMAT_MULTILINE));
1063
1064                 final NumericFormat numericFormatting = formatting.getNumericFormat();
1065                 if (numericFormatting != null) {
1066                         numericFormatting.setUseThousandsSeparator(getAttributeAsBoolean(elementFormatting,
1067                                         ATTRIBUTE_USE_THOUSANDS_SEPARATOR));
1068                         numericFormatting
1069                                         .setDecimalPlaces((int) getAttributeAsDecimal(elementFormatting, ATTRIBUTE_DECIMAL_PLACES));
1070                 }
1071
1072         }
1073
1074         /**
1075          * @param element
1076          * @param reportNode
1077          */
1078         private void loadReport(final Element element, final Report report, final String tableName) {
1079                 report.setName(element.getAttribute(ATTRIBUTE_NAME));
1080                 loadTitle(element, report);
1081
1082                 final List<LayoutGroup> listLayoutGroups = loadLayoutNode(element, tableName);
1083
1084                 // A report can actually only have one LayoutGroup,
1085                 // though it uses the same XML structure as List and Details layouts,
1086                 // which (wrongly) suggests that it can have more than one group.
1087                 LayoutGroup layoutGroup = null;
1088                 if (!listLayoutGroups.isEmpty()) {
1089                         layoutGroup = listLayoutGroups.get(0);
1090                 }
1091
1092                 report.setLayoutGroup(layoutGroup);
1093         }
1094
1095         private TableInfo getTableInfo(final String tableName) {
1096                 return tablesMap.get(tableName);
1097         }
1098
1099         public enum HostingMode {
1100                 HOSTING_MODE_POSTGRES_CENTRAL, HOSTING_MODE_POSTGRES_SELF, HOSTING_MODE_SQLITE
1101         };
1102
1103         public String getDatabaseTitle(final String locale) {
1104                 return databaseTitle.getTitle(locale);
1105         }
1106
1107         public String getDatabaseTitleOriginal() {
1108                 return databaseTitle.getTitleOriginal();
1109         }
1110
1111         public List<String> getTranslationAvailableLocales() {
1112                 return translationAvailableLocales;
1113         }
1114
1115         public Document.HostingMode getHostingMode() {
1116                 return hostingMode;
1117         }
1118
1119         /**
1120          * @param hostingMode
1121          */
1122         public void setHostingMode(final HostingMode hostingMode) {
1123                 this.hostingMode = hostingMode;
1124         }
1125
1126         public String getConnectionServer() {
1127                 return connectionServer;
1128         }
1129
1130         public int getConnectionPort() {
1131                 return connectionPort;
1132         }
1133
1134         public void setConnectionPort(final int port) {
1135                 connectionPort = port;
1136         }
1137
1138         public String getConnectionDatabase() {
1139                 return connectionDatabase;
1140         }
1141
1142         public List<String> getTableNames() {
1143                 // TODO: Return a Set?
1144                 return new ArrayList<String>(tablesMap.keySet());
1145         }
1146
1147         public boolean getTableIsHidden(final String tableName) {
1148                 final TableInfo info = getTableInfo(tableName);
1149                 if (info == null) {
1150                         return false;
1151                 }
1152
1153                 return info.isHidden;
1154         }
1155
1156         public String getTableTitle(final String tableName, final String locale) {
1157                 final TableInfo info = getTableInfo(tableName);
1158                 if (info == null) {
1159                         return "";
1160                 }
1161
1162                 return info.getTitle(locale);
1163         }
1164
1165         public List<Map<String, DataItem>> getExampleRows(final String tableName) {
1166                 final TableInfo info = getTableInfo(tableName);
1167                 if (info == null) {
1168                         return null;
1169                 }
1170
1171                 return info.exampleRows;
1172         }
1173
1174         public String getDefaultTable() {
1175                 for (final TableInfo info : tablesMap.values()) {
1176                         if (info.isDefault) {
1177                                 return info.getName();
1178                         }
1179                 }
1180
1181                 return "";
1182         }
1183
1184         public boolean getTableIsKnown(final String tableName) {
1185                 final TableInfo info = getTableInfo(tableName);
1186                 if (info == null) {
1187                         return false;
1188                 }
1189
1190                 return true;
1191         }
1192
1193         public List<Field> getTableFields(final String tableName) {
1194                 final TableInfo info = getTableInfo(tableName);
1195                 if (info == null) {
1196                         return null;
1197                 }
1198
1199                 return new ArrayList<Field>(info.fieldsMap.values());
1200         }
1201
1202         public Field getField(final String tableName, final String strFieldName) {
1203                 final TableInfo info = getTableInfo(tableName);
1204                 if (info == null) {
1205                         return null;
1206                 }
1207
1208                 return info.fieldsMap.get(strFieldName);
1209         }
1210
1211         public List<LayoutGroup> getDataLayoutGroups(final String layoutName, final String parentTableName) {
1212                 final TableInfo info = getTableInfo(parentTableName);
1213                 if (info == null) {
1214                         return new ArrayList<LayoutGroup>();
1215                 }
1216
1217                 if (layoutName == LAYOUT_NAME_DETAILS) {
1218                         return info.layoutGroupsDetails;
1219                 } else if (layoutName == LAYOUT_NAME_LIST) {
1220                         return info.layoutGroupsList;
1221                 } else {
1222                         return new ArrayList<LayoutGroup>();
1223                 }
1224         }
1225
1226         public List<String> getReportNames(final String tableName) {
1227                 final TableInfo info = getTableInfo(tableName);
1228                 if (info == null) {
1229                         return new ArrayList<String>();
1230                 }
1231
1232                 return new ArrayList<String>(info.reportsMap.keySet());
1233         }
1234
1235         public Report getReport(final String tableName, final String reportName) {
1236                 final TableInfo info = getTableInfo(tableName);
1237                 if (info == null) {
1238                         return null;
1239                 }
1240
1241                 return info.reportsMap.get(reportName);
1242         }
1243
1244         /**
1245          * @param tableName
1246          * @param field
1247          * @return
1248          */
1249         public Relationship getFieldUsedInRelationshipToOne(final String tableName, final LayoutItemField layoutField) {
1250
1251                 if (layoutField == null) {
1252                         Log.error("layoutField was null");
1253                         return null;
1254                 }
1255
1256                 Relationship result = null;
1257
1258                 final String tableUsed = layoutField.getTableUsed(tableName);
1259                 final TableInfo info = getTableInfo(tableUsed);
1260                 if (info == null) {
1261                         // This table is special. We would not create a relationship to it using a field:
1262                         // if(tableUsed == GLOM_STANDARD_TABLE_PREFS_TABLE_NAME)
1263                         // return result;
1264
1265                         Log.error("table not found: " + tableUsed);
1266                         return null;
1267                 }
1268
1269                 // Look at each relationship:
1270                 final String fieldName = layoutField.getName();
1271                 for (final Relationship relationship : info.relationshipsMap.values()) {
1272                         if (relationship != null) {
1273                                 // If the relationship uses the field
1274                                 if (StringUtils.equals(relationship.getFromField(), fieldName)) {
1275                                         // if the to_table is not hidden:
1276                                         if (!getTableIsHidden(relationship.getToTable())) {
1277                                                 // TODO_Performance: The use of this convenience method means we get the full relationship
1278                                                 // information again:
1279                                                 if (getRelationshipIsToOne(tableName, relationship.getName())) {
1280                                                         result = relationship;
1281                                                 }
1282                                         }
1283                                 }
1284                         }
1285                 }
1286
1287                 return result;
1288         }
1289
1290         /**
1291          * @param tableName
1292          * @param relationshipName
1293          * @return
1294          */
1295         private boolean getRelationshipIsToOne(final String tableName, final String relationshipName) {
1296                 final Relationship relationship = getRelationship(tableName, relationshipName);
1297                 if (relationship != null) {
1298                         final Field fieldTo = getField(relationship.getToTable(), relationship.getToField());
1299                         if (fieldTo != null) {
1300                                 return (fieldTo.getPrimaryKey() || fieldTo.getUniqueKey());
1301                         }
1302                 }
1303
1304                 return false;
1305         }
1306
1307         /**
1308          * @param tableName
1309          * @param relationshipName
1310          * @return
1311          */
1312         private Relationship getRelationship(final String tableName, final String relationshipName) {
1313                 final TableInfo info = getTableInfo(tableName);
1314                 if (info == null) {
1315                         Log.error("table not found: " + tableName);
1316                         return null;
1317                 }
1318
1319                 return info.relationshipsMap.get(relationshipName);
1320         }
1321
1322         /**
1323          * @param tableName
1324          *            Output parameter
1325          * @param relationship
1326          * @param portal
1327          *            TODO
1328          */
1329         public TableToViewDetails getPortalSuitableTableToViewDetails(final LayoutItemPortal portal) {
1330                 UsesRelationship navigationRelationship = null;
1331
1332                 // Check whether a relationship was specified:
1333                 if (portal.getNavigationType() == NavigationType.NAVIGATION_AUTOMATIC) {
1334                         navigationRelationship = getPortalNavigationRelationshipAutomatic(portal);
1335                 } else {
1336                         navigationRelationship = portal.getNavigationRelationshipSpecific();
1337                 }
1338
1339                 // Get the navigation table name from the chosen relationship:
1340                 final String directlyRelatedTableName = portal.getTableUsed("" /* not relevant */);
1341
1342                 // The navigation_table_name (and therefore, the table_name output parameter,
1343                 // as well) stays empty if the navrel type was set to none.
1344                 String navigationTableName = null;
1345                 if (navigationRelationship != null) {
1346                         navigationTableName = navigationRelationship.getTableUsed(directlyRelatedTableName);
1347                 } else if (portal.getNavigationType() != NavigationType.NAVIGATION_NONE) {
1348                         // An empty result from get_portal_navigation_relationship_automatic() or
1349                         // get_navigation_relationship_specific() means we should use the directly related table:
1350                         navigationTableName = directlyRelatedTableName;
1351                 }
1352
1353                 if (StringUtils.isEmpty(navigationTableName)) {
1354                         return null;
1355                 }
1356
1357                 if (this == null) {
1358                         Log.error("document is null.");
1359                         return null;
1360                 }
1361
1362                 if (getTableIsHidden(navigationTableName)) {
1363                         Log.error("navigation_table_name indicates a hidden table: " + navigationTableName);
1364                         return null;
1365                 }
1366
1367                 final TableToViewDetails result = new TableToViewDetails();
1368                 result.tableName = navigationTableName;
1369                 result.usesRelationship = navigationRelationship;
1370                 return result;
1371         }
1372
1373         /**
1374          * @param portal
1375          *            TODO
1376          * @return
1377          */
1378         private UsesRelationship getPortalNavigationRelationshipAutomatic(final LayoutItemPortal portal) {
1379                 if (this == null) {
1380                         return null;
1381                 }
1382
1383                 // If the related table is not hidden then we can just navigate to that:
1384                 final String direct_related_table_name = portal.getTableUsed("" /* parent table - not relevant */);
1385                 if (!getTableIsHidden(direct_related_table_name)) {
1386                         // Non-hidden tables can just be shown directly. Navigate to it:
1387                         return null;
1388                 } else {
1389                         // If the related table is hidden,
1390                         // then find a suitable related non-hidden table by finding the first layout field that mentions one:
1391                         final LayoutItemField field = getPortalFieldIsFromNonHiddenRelatedRecord(portal);
1392                         if (field != null) {
1393                                 return field; // Returns the UsesRelationship base part. (A relationship belonging to the portal's
1394                                                                 // related table.)
1395                         } else {
1396                                 // Instead, find a key field that's used in a relationship,
1397                                 // and pretend that we are showing the to field as a related field:
1398                                 final Relationship fieldIndentifies = getPortalFieldIdentifiesNonHiddenRelatedRecord(portal);
1399                                 if (fieldIndentifies != null) {
1400                                         final UsesRelationship result = new UsesRelationshipImpl();
1401                                         result.setRelationship(fieldIndentifies);
1402                                         return result;
1403                                 }
1404                         }
1405                 }
1406
1407                 // There was no suitable related table to show:
1408                 return null;
1409         }
1410
1411         /**
1412          * @param portal
1413          *            TODO
1414          * @return
1415          */
1416         private LayoutItemField getPortalFieldIsFromNonHiddenRelatedRecord(final LayoutItemPortal portal) {
1417                 // Find the first field that is from a non-hidden related table.
1418
1419                 if (this == null) {
1420                         return null;
1421                 }
1422
1423                 final LayoutItemField result = null;
1424
1425                 final String parent_table_name = portal.getTableUsed("" /* parent table - not relevant */);
1426
1427                 final List<LayoutItem> items = portal.getItems();
1428                 for (final LayoutItem item : items) {
1429                         if (item instanceof LayoutItemField) {
1430                                 final LayoutItemField field = (LayoutItemField) item;
1431                                 if (field.getHasRelationshipName()) {
1432                                         final String table_name = field.getTableUsed(parent_table_name);
1433                                         if (!(getTableIsHidden(table_name))) {
1434                                                 return field;
1435                                         }
1436                                 }
1437                         }
1438                 }
1439
1440                 return result;
1441         }
1442
1443         /**
1444          * @param used_in_relationship
1445          * @param portal
1446          *            TODO
1447          * @return
1448          */
1449         private Relationship getPortalFieldIdentifiesNonHiddenRelatedRecord(final LayoutItemPortal portal) {
1450                 // Find the first field that is from a non-hidden related table.
1451
1452                 if (this == null) {
1453                         Log.error("document is null");
1454                         return null;
1455                 }
1456
1457                 final String parent_table_name = portal.getTableUsed("" /* parent table - not relevant */);
1458
1459                 final List<LayoutItem> items = portal.getItems();
1460                 for (final LayoutItem item : items) {
1461                         if (item instanceof LayoutItemField) {
1462                                 final LayoutItemField field = (LayoutItemField) item;
1463                                 if (field.getHasRelationshipName()) {
1464                                         final Relationship relationship = getFieldUsedInRelationshipToOne(parent_table_name, field);
1465                                         if (relationship != null) {
1466                                                 final String table_name = relationship.getToTable();
1467                                                 if (!StringUtils.isEmpty(table_name)) {
1468                                                         if (!(getTableIsHidden(table_name))) {
1469                                                                 return relationship;
1470                                                         }
1471                                                 }
1472                                         }
1473                                 }
1474                         }
1475                 }
1476
1477                 return null;
1478         }
1479
1480         /**
1481          * @param tableName
1482          * @param layoutItem
1483          * @return The destination table name for navigation.
1484          */
1485         public String getLayoutItemFieldShouldHaveNavigation(final String tableName, final LayoutItemField layoutItem) {
1486                 if (StringUtils.isEmpty(tableName)) {
1487                         return null;
1488                 }
1489
1490                 if (layoutItem == null) {
1491                         return null;
1492                 }
1493
1494                 // Check whether the field controls a relationship,
1495                 // meaning it identifies a record in another table.
1496                 final Relationship fieldUsedInRelationshipToOne = getFieldUsedInRelationshipToOne(tableName, layoutItem);
1497                 if (fieldUsedInRelationshipToOne != null) {
1498                         return fieldUsedInRelationshipToOne.getToTable();
1499                 }
1500
1501                 // Check whether the field identifies a record in another table
1502                 // just because it is a primary key in that table:
1503                 final Field fieldInfo = layoutItem.getFullFieldDetails();
1504                 final boolean fieldIsRelatedPrimaryKey = layoutItem.getHasRelationshipName() && (fieldInfo != null)
1505                                 && fieldInfo.getPrimaryKey();
1506                 if (fieldIsRelatedPrimaryKey) {
1507                         return layoutItem.getRelationship().getToTable();
1508                 }
1509
1510                 return null;
1511         }
1512
1513         /**
1514          * @param isExample
1515          */
1516         public void setIsExampleFile(final boolean isExample) {
1517                 this.isExample = isExample;
1518         }
1519
1520         /**
1521          */
1522         public boolean getIsExampleFile() {
1523                 return isExample;
1524         }
1525
1526         /**
1527          * @return
1528          */
1529         public boolean save() {
1530                 final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
1531                 DocumentBuilder documentBuilder = null;
1532                 try {
1533                         documentBuilder = dbf.newDocumentBuilder();
1534                 } catch (final ParserConfigurationException e) {
1535                         // TODO Auto-generated catch block
1536                         e.printStackTrace();
1537                         return false;
1538                 }
1539
1540                 final org.w3c.dom.Document doc = documentBuilder.newDocument();
1541                 final Element rootNode = doc.createElement(NODE_ROOT);
1542                 doc.appendChild(rootNode);
1543
1544                 rootNode.setAttribute(ATTRIBUTE_TITLE, databaseTitle.getTitleOriginal());
1545                 rootNode.setAttribute(ATTRIBUTE_TRANSLATION_ORIGINAL_LOCALE, translationOriginalLocale);
1546                 setAttributeAsBoolean(rootNode, ATTRIBUTE_IS_EXAMPLE, isExample);
1547
1548                 String strHostingMode = "";
1549                 if (hostingMode == HostingMode.HOSTING_MODE_POSTGRES_CENTRAL) {
1550                         strHostingMode = "postgres_central";
1551                 } else if (hostingMode == HostingMode.HOSTING_MODE_SQLITE) {
1552                         strHostingMode = "sqlite";
1553                 } else {
1554                         strHostingMode = "postgres_self";
1555                 }
1556                 final Element nodeConnection = createElement(doc, rootNode, NODE_CONNECTION);
1557                 nodeConnection.setAttribute(ATTRIBUTE_CONNECTION_HOSTING_MODE, strHostingMode);
1558                 nodeConnection.setAttribute(ATTRIBUTE_CONNECTION_SERVER, connectionServer);
1559                 nodeConnection.setAttribute(ATTRIBUTE_CONNECTION_DATABASE, connectionDatabase);
1560                 setAttributeAsDecimal(nodeConnection, ATTRIBUTE_CONNECTION_PORT, connectionPort);
1561
1562                 // for all tables:
1563                 for (final TableInfo table : tablesMap.values()) {
1564                         final Element nodeTable = createElement(doc, rootNode, NODE_TABLE);
1565                         saveTableNodeBasic(doc, nodeTable, table);
1566                         saveTableLayouts(doc, nodeTable, table);
1567                 }
1568
1569                 final TransformerFactory transformerFactory = TransformerFactory.newInstance();
1570                 Transformer transformer;
1571                 try {
1572                         transformer = transformerFactory.newTransformer();
1573                 } catch (final TransformerConfigurationException e) {
1574                         // TODO Auto-generated catch block
1575                         e.printStackTrace();
1576                         return false;
1577                 }
1578
1579                 // TODO: This probably distorts text nodes,
1580                 // so careful when we load/save them. For instance, scripts.
1581                 transformer.setOutputProperty(OutputKeys.INDENT, "yes");
1582                 transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
1583
1584                 // Make sure that the parent directory exists:
1585                 final File file = new File(fileURI);
1586                 try {
1587                         Files.createParentDirs(file);
1588                 } catch (final IOException e) {
1589                         e.printStackTrace();
1590                         return false;
1591                 }
1592
1593                 final DOMSource source = new DOMSource(doc);
1594                 final StreamResult result = new StreamResult(file);
1595
1596                 // Output to console for testing
1597                 // StreamResult result = new StreamResult(System.out);
1598
1599                 try {
1600                         transformer.transform(source, result);
1601                 } catch (final TransformerException e) {
1602                         // TODO Auto-generated catch block
1603                         e.printStackTrace();
1604                         return false;
1605                 }
1606
1607                 return true;
1608         }
1609
1610         /**
1611          * @param doc
1612          * @param nodeTable
1613          * @param table
1614          */
1615         private void saveTableLayouts(final org.w3c.dom.Document doc, final Element tableNode, final TableInfo table) {
1616
1617                 final Element layoutsNode = createElement(doc, tableNode, NODE_DATA_LAYOUTS);
1618
1619                 final Element nodeLayoutDetails = createElement(doc, layoutsNode, NODE_DATA_LAYOUT);
1620                 nodeLayoutDetails.setAttribute(ATTRIBUTE_NAME, LAYOUT_NAME_DETAILS);
1621                 saveLayoutNode(doc, nodeLayoutDetails, table.layoutGroupsDetails);
1622
1623                 final Element nodeLayoutList = createElement(doc, layoutsNode, NODE_DATA_LAYOUT);
1624                 nodeLayoutList.setAttribute(ATTRIBUTE_NAME, LAYOUT_NAME_LIST);
1625                 saveLayoutNode(doc, nodeLayoutList, table.layoutGroupsList);
1626
1627                 final Element reportsNode = createElement(doc, tableNode, NODE_REPORTS);
1628                 for (final Report report : table.reportsMap.values()) {
1629                         final Element element = createElement(doc, reportsNode, NODE_REPORT);
1630                         saveReport(doc, element, report);
1631                 }
1632
1633         }
1634
1635         /**
1636          * @param doc
1637          * @param element
1638          * @param report
1639          */
1640         private void saveReport(final org.w3c.dom.Document doc, final Element element, final Report report) {
1641                 // TODO Auto-generated method stub
1642
1643         }
1644
1645         private void saveLayoutNode(final org.w3c.dom.Document doc, final Element element,
1646                         final List<LayoutGroup> layoutGroups) {
1647                 final Element elementGroups = createElement(doc, element, NODE_DATA_LAYOUT_GROUPS);
1648
1649                 for (final LayoutGroup layoutGroup : layoutGroups) {
1650                         if (layoutGroup instanceof LayoutItemNotebook) {
1651                                 final Element elementGroup = createElement(doc, elementGroups, NODE_DATA_LAYOUT_NOTEBOOK);
1652                                 saveDataLayoutGroup(doc, elementGroup, layoutGroup);
1653                         } else if (layoutGroup instanceof LayoutItemPortal) {
1654                                 final Element elementGroup = createElement(doc, elementGroups, NODE_DATA_LAYOUT_PORTAL);
1655                                 saveDataLayoutPortal(doc, elementGroup, (LayoutItemPortal) layoutGroup);
1656                         } else {
1657                                 final Element elementGroup = createElement(doc, elementGroups, NODE_DATA_LAYOUT_GROUP);
1658                                 saveDataLayoutGroup(doc, elementGroup, layoutGroup);
1659                         }
1660                 }
1661
1662         }
1663
1664         /**
1665          * @param doc
1666          * @param elementGroup
1667          * @param layoutGroup
1668          */
1669         private void saveDataLayoutPortal(final org.w3c.dom.Document doc, final Element element,
1670                         final LayoutItemPortal portal) {
1671                 saveUsesRelationship(element, portal);
1672                 saveDataLayoutGroup(doc, element, portal);
1673
1674                 final Element elementNavigation = createElement(doc, element, NODE_DATA_LAYOUT_PORTAL_NAVIGATIONRELATIONSHIP);
1675                 String navigationTypeAsString = "";
1676                 switch (portal.getNavigationType()) {
1677                 case NAVIGATION_AUTOMATIC:
1678                         navigationTypeAsString = ATTRIBUTE_PORTAL_NAVIGATION_TYPE_AUTOMATIC;
1679                         break;
1680                 case NAVIGATION_NONE:
1681                         navigationTypeAsString = ATTRIBUTE_PORTAL_NAVIGATION_TYPE_NONE;
1682                         break;
1683                 case NAVIGATION_SPECIFIC:
1684                         navigationTypeAsString = ATTRIBUTE_PORTAL_NAVIGATION_TYPE_SPECIFIC;
1685                         break;
1686                 default:
1687                         break;
1688                 }
1689                 elementNavigation.setAttribute(ATTRIBUTE_PORTAL_NAVIGATION_TYPE, navigationTypeAsString);
1690
1691                 if (navigationTypeAsString == ATTRIBUTE_PORTAL_NAVIGATION_TYPE_SPECIFIC) {
1692                         // Write the specified relationship name:
1693                         saveUsesRelationship(elementNavigation, portal.getNavigationRelationshipSpecific());
1694                 }
1695         }
1696
1697         /**
1698          * @param doc
1699          * @param elementGroup
1700          * @param layoutGroup
1701          */
1702         private void saveDataLayoutGroup(final org.w3c.dom.Document doc, final Element nodeGroup, final LayoutGroup group) {
1703                 saveTitle(doc, nodeGroup, group);
1704
1705                 // Write the column count:
1706                 setAttributeAsDecimal(nodeGroup, ATTRIBUTE_LAYOUT_GROUP_COLUMNS_COUNT, group.getColumnCount());
1707
1708                 // Write the child items:
1709                 for (final LayoutItem layoutItem : group.getItems()) {
1710                         if (layoutItem instanceof LayoutItemPortal) {
1711                                 final Element element = createElement(doc, nodeGroup, NODE_DATA_LAYOUT_PORTAL);
1712                                 saveDataLayoutPortal(doc, element, (LayoutItemPortal) layoutItem);
1713                         } else if (layoutItem instanceof LayoutItemNotebook) {
1714                                 final Element element = createElement(doc, nodeGroup, NODE_DATA_LAYOUT_NOTEBOOK);
1715                                 saveDataLayoutGroup(doc, element, (LayoutItemNotebook) layoutItem);
1716                         } else if (layoutItem instanceof LayoutGroup) {
1717                                 final Element element = createElement(doc, nodeGroup, NODE_DATA_LAYOUT_GROUP);
1718                                 saveDataLayoutGroup(doc, element, (LayoutGroup) layoutItem);
1719                         } else if (layoutItem instanceof LayoutItemField) {
1720                                 final Element element = createElement(doc, nodeGroup, NODE_DATA_LAYOUT_ITEM);
1721                                 saveDataLayoutItemField(doc, element, (LayoutItemField) layoutItem);
1722                         } else if (layoutItem instanceof LayoutItemGroupBy) {
1723                                 final Element element = createElement(doc, nodeGroup, NODE_DATA_LAYOUT_ITEM_GROUPBY);
1724                                 saveDataLayoutItemGroupBy(doc, element, (LayoutItemGroupBy) layoutItem);
1725                         }
1726                 }
1727         }
1728
1729         /**
1730          * @param doc
1731          * @param element
1732          * @param layoutItem
1733          */
1734         private void saveDataLayoutItemGroupBy(final org.w3c.dom.Document doc, final Element element,
1735                         final LayoutItemGroupBy item) {
1736                 saveDataLayoutGroup(doc, element, item);
1737
1738                 final Element elementGroupBy = createElement(doc, element, NODE_GROUPBY);
1739                 saveDataLayoutItemField(doc, elementGroupBy, item.getFieldGroupBy());
1740
1741                 final Element elementSecondaryFields = createElement(doc, element, NODE_SECONDARY_FIELDS);
1742                 final Element elementLayoutGroup = createElement(doc, elementSecondaryFields, NODE_DATA_LAYOUT_GROUP);
1743                 saveDataLayoutGroup(doc, elementLayoutGroup, item.getSecondaryFields());
1744         }
1745
1746         /**
1747          * @param doc
1748          * @param element
1749          * @param layoutItem
1750          */
1751         private void saveDataLayoutItemField(final org.w3c.dom.Document doc, final Element element,
1752                         final LayoutItemField item) {
1753                 element.setAttribute(ATTRIBUTE_NAME, item.getName());
1754                 saveUsesRelationship(element, item);
1755
1756                 final CustomTitle customTitle = item.getCustomTitle();
1757                 if (customTitle != null) {
1758                         final Element elementCustomTitle = createElement(doc, element, NODE_CUSTOM_TITLE);
1759                         setAttributeAsBoolean(elementCustomTitle, ATTRIBUTE_CUSTOM_TITLE_USE_CUSTOM,
1760                                         customTitle.getUseCustomTitle());
1761                         saveTitle(doc, elementCustomTitle, customTitle); // LayoutItemField doesn't use its own title member.
1762                 }
1763
1764                 setAttributeAsBoolean(element, ATTRIBUTE_USE_DEFAULT_FORMATTING, item.getUseDefaultFormatting());
1765
1766                 final Element elementFormatting = createElement(doc, element, NODE_FORMATTING);
1767                 saveFormatting(elementFormatting, item.getFormatting());
1768         }
1769
1770         /**
1771          * @param element
1772          * @param item
1773          */
1774         private void saveUsesRelationship(final Element element, final UsesRelationship item) {
1775                 final Relationship relationship = item.getRelationship();
1776                 if (relationship != null) {
1777                         element.setAttribute(ATTRIBUTE_RELATIONSHIP_NAME, relationship.getName());
1778                 }
1779
1780                 final Relationship relatedRelationship = item.getRelatedRelationship();
1781                 if (relatedRelationship != null) {
1782                         element.setAttribute(ATTRIBUTE_RELATED_RELATIONSHIP_NAME, relatedRelationship.getName());
1783                 }
1784         }
1785
1786         /**
1787          * @param rootNode
1788          * @param nodeConnection
1789          * @return
1790          */
1791         private Element createElement(final org.w3c.dom.Document doc, final Element parentNode, final String name) {
1792                 final Element node = doc.createElement(name);
1793                 parentNode.appendChild(node);
1794                 return node;
1795         }
1796
1797         public String getSelfHostedDirectoryPath() {
1798                 final String uriFile = getFileURI();
1799                 if (!StringUtils.isEmpty(uriFile)) {
1800                         final File file = new File(uriFile);
1801                         final File parent = file.getParentFile();
1802                         if (parent == null) {
1803                                 // TODO: Warn.
1804                                 return "";
1805                         }
1806
1807                         File dataDir = null;
1808                         switch (hostingMode) {
1809                         case HOSTING_MODE_POSTGRES_SELF:
1810                                 dataDir = new File(parent, "glom_postgres_data");
1811                                 break;
1812                         case HOSTING_MODE_POSTGRES_CENTRAL:
1813                                 dataDir = parent;
1814                                 break;
1815                         case HOSTING_MODE_SQLITE:
1816                                 dataDir = parent;
1817                                 break;
1818                         default:
1819                                 // TODO: Warn.
1820                                 break;
1821                         }
1822
1823                         if (dataDir != null) {
1824                                 return dataDir.getPath();
1825                         }
1826                 }
1827
1828                 // TODO: std::cerr << G_STRFUNC << ": returning empty string." << std::endl;
1829                 return "";
1830         }
1831
1832         /**
1833          */
1834         public void setConnectionDatabase(final String databaseName) {
1835                 connectionDatabase = databaseName;
1836
1837         }
1838
1839         /**
1840          * Gets the primary key Field for the specified table name.
1841          * 
1842          * @param tableName
1843          *            name of table to search for the primary key field
1844          * @return primary key Field
1845          */
1846         public Field getTablePrimaryKeyField(final String tableName) {
1847                 Field primaryKey = null;
1848                 final List<Field> fieldsVec = getTableFields(tableName);
1849                 for (int i = 0; i < Utils.safeLongToInt(fieldsVec.size()); i++) {
1850                         final Field field = fieldsVec.get(i);
1851                         if (field.getPrimaryKey()) {
1852                                 primaryKey = field;
1853                                 break;
1854                         }
1855                 }
1856                 return primaryKey;
1857         }
1858 }