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