Document: Correct the port number parsing.
[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.IOException;
23 import java.util.ArrayList;
24 import java.util.Hashtable;
25 import java.util.List;
26
27 import javax.xml.parsers.DocumentBuilder;
28 import javax.xml.parsers.DocumentBuilderFactory;
29 import javax.xml.parsers.ParserConfigurationException;
30
31 import org.apache.commons.lang3.StringUtils;
32 import org.glom.web.shared.libglom.Field;
33 import org.glom.web.shared.libglom.NumericFormat;
34 import org.glom.web.shared.libglom.Relationship;
35 import org.glom.web.shared.libglom.Report;
36 import org.glom.web.shared.libglom.Translatable;
37 import org.glom.web.shared.libglom.layout.Formatting;
38 import org.glom.web.shared.libglom.layout.LayoutGroup;
39 import org.glom.web.shared.libglom.layout.LayoutItem;
40 import org.glom.web.shared.libglom.layout.LayoutItemField;
41 import org.glom.web.shared.libglom.layout.LayoutItemNotebook;
42 import org.glom.web.shared.libglom.layout.LayoutItemPortal;
43 import org.glom.web.shared.libglom.layout.LayoutItemPortal.NavigationType;
44 import org.glom.web.shared.libglom.layout.UsesRelationship;
45 import org.glom.web.shared.libglom.layout.UsesRelationshipImpl;
46 import org.glom.web.shared.libglom.layout.reportparts.LayoutItemGroupBy;
47 import org.jfree.util.Log;
48 import org.w3c.dom.Element;
49 import org.w3c.dom.Node;
50 import org.w3c.dom.NodeList;
51 import org.xml.sax.SAXException;
52
53 /**
54  * @author Murray Cumming <murrayc@openismus.com>
55  * 
56  */
57 public class Document {
58
59         @SuppressWarnings("serial")
60         private static class TableInfo extends Translatable {
61                 public boolean isDefault;
62                 public boolean isHidden;
63
64                 private final Hashtable<String, Field> fieldsMap = new Hashtable<String, Field>();
65                 private final Hashtable<String, Relationship> relationshipsMap = new Hashtable<String, Relationship>();
66                 private final Hashtable<String, Report> reportsMap = new Hashtable<String, Report>();
67
68                 private List<LayoutGroup> layoutGroupsList = new ArrayList<LayoutGroup>();
69                 private List<LayoutGroup> layoutGroupsDetails = new ArrayList<LayoutGroup>();
70         }
71
72         private String fileURI = "";
73         private org.w3c.dom.Document xmlDocument = null;
74
75         private Translatable databaseTitle = new Translatable();
76         private List<String> translationAvailableLocales = new ArrayList<String>(); // TODO
77         private String connectionServer = "";
78         private String connectionDatabase = "";
79         private int connectionPort = 0;
80         private final Hashtable<String, TableInfo> tablesMap = new Hashtable<String, TableInfo>();
81
82         private static final String NODE_CONNECTION = "connection";
83         private static final String ATTRIBUTE_CONNECTION_SERVER = "server";
84         private static final String ATTRIBUTE_CONNECTION_DATABASE = "database";
85         private static final String ATTRIBUTE_CONNECTION_PORT = "port";
86         private static final String NODE_TABLE = "table";
87         private static final String ATTRIBUTE_NAME = "name";
88         private static final String ATTRIBUTE_TITLE = "title";
89         private static final String ATTRIBUTE_DEFAULT = "default";
90         private static final String ATTRIBUTE_HIDDEN = "hidden";
91         private static final String NODE_TRANSLATIONS_SET = "trans_set";
92         private static final String NODE_TRANSLATIONS = "trans";
93         private static final String ATTRIBUTE_TRANSLATION_LOCALE = "loc";
94         private static final String ATTRIBUTE_TRANSLATION_TITLE = "val";
95         private static final String NODE_REPORTS = "reports";
96         private static final String NODE_REPORT = "report";
97         private static final String NODE_FIELDS = "fields";
98         private static final String NODE_FIELD = "field";
99         private static final String ATTRIBUTE_PRIMARY_KEY = "primary_key";
100         private static final String ATTRIBUTE_FIELD_TYPE = "type";
101         private static final String NODE_FORMATTING = "formatting";
102         //private static final String ATTRIBUTE_TEXT_FORMAT_MULTILINE = "format_text_multiline";
103         private static final String ATTRIBUTE_USE_THOUSANDS_SEPARATOR = "format_thousands_separator";
104         private static final String ATTRIBUTE_DECIMAL_PLACES = "format_decimal_places";
105         private static final String NODE_RELATIONSHIPS = "relationships";
106         private static final String NODE_RELATIONSHIP = "relationship";
107         private static final String ATTRIBUTE_RELATIONSHIP_FROM_FIELD = "key";
108         private static final String ATTRIBUTE_RELATIONSHIP_TO_TABLE = "other_table";
109         private static final String ATTRIBUTE_RELATIONSHIP_TO_FIELD = "other_key";
110         private static final String NODE_DATA_LAYOUTS = "data_layouts";
111         private static final String NODE_DATA_LAYOUT = "data_layout";
112         private static final String NODE_DATA_LAYOUT_GROUPS = "data_layout_groups";
113         private static final String NODE_DATA_LAYOUT_GROUP = "data_layout_group";
114         private static final String ATTRIBUTE_LAYOUT_GROUP_COLUMNS_COUNT = "columns_count";
115         private static final String NODE_DATA_LAYOUT_NOTEBOOK = "data_layout_notebook";
116         private static final String NODE_DATA_LAYOUT_PORTAL = "data_layout_portal";
117         private static final String NODE_DATA_LAYOUT_PORTAL_NAVIGATIONRELATIONSHIP = "portal_navigation_relationship";
118         private static final String ATTRIBUTE_PORTAL_NAVIGATION_TYPE = "navigation_type";
119         private static final String ATTRIBUTE_PORTAL_NAVIGATION_TYPE_AUTOMATIC = "automatic";
120         private static final String ATTRIBUTE_PORTAL_NAVIGATION_TYPE_SPECIFIC = "specific";
121         private static final String ATTRIBUTE_PORTAL_NAVIGATION_TYPE_NONE = "none";
122         private static final String ATTRIBUTE_RELATIONSHIP_NAME = "relationship";
123         private static final String ATTRIBUTE_RELATED_RELATIONSHIP_NAME = "related_relationship";
124         private static final String NODE_DATA_LAYOUT_ITEM = "data_layout_item";
125         private static final String NODE_DATA_LAYOUT_ITEM_GROUPBY = "data_layout_item_groupby";
126         private static final String NODE_GROUPBY = "groupby";
127         private static final String NODE_SECONDARY_FIELDS = "secondary_fields";
128         private static final String ATTRIBUTE_USE_DEFAULT_FORMATTING = "use_default_formatting";
129         private static final String LAYOUT_NAME_DETAILS = "details";
130         private static final String LAYOUT_NAME_LIST = "list";
131
132         public void setFileURI(final String fileURI) {
133                 this.fileURI = fileURI;
134         }
135
136         public String getFileURI() {
137                 return fileURI;
138         }
139
140         // TODO: Make sure these have the correct values.
141         public enum LoadFailureCodes {
142                 LOAD_FAILURE_CODE_NONE, LOAD_FAILURE_CODE_NOT_FOUND, LOAD_FAILURE_CODE_FILE_VERSION_TOO_NEW
143         };
144
145         public boolean load() {
146                 final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
147                 DocumentBuilder documentBuilder;
148                 try {
149                         documentBuilder = dbf.newDocumentBuilder();
150                 } catch (final ParserConfigurationException e) {
151                         // TODO Auto-generated catch block
152                         e.printStackTrace();
153                         return false;
154                 }
155
156                 try {
157                         xmlDocument = documentBuilder.parse(fileURI);
158                 } catch (final SAXException e) {
159                         // TODO Auto-generated catch block
160                         e.printStackTrace();
161                         return false;
162                 } catch (final IOException e) {
163                         // TODO Auto-generated catch block
164                         e.printStackTrace();
165                         return false;
166                 }
167
168                 final Element rootNode = xmlDocument.getDocumentElement();
169                 if (rootNode.getNodeName() != "glom_document") {
170                         Log.error("Unexpected XML root node name found: " + rootNode.getNodeName());
171                         return false;
172                 }
173
174                 databaseTitle.setTitleOriginal(rootNode.getAttribute(ATTRIBUTE_TITLE));
175
176                 // We first load the fields, relationships, etc,
177                 // for all tables:
178                 final List<Node> listTableNodes = getChildrenByTagName(rootNode, NODE_TABLE);
179                 for (final Node node : listTableNodes) {
180                         if (!(node instanceof Element))
181                                 continue;
182
183                         final Element element = (Element) node;
184                         final TableInfo info = loadTableNodeBasic(element);
185                         tablesMap.put(info.getName(), info);
186                 }
187
188                 // We then load the layouts for all tables, because they
189                 // need the fields and relationships for all tables:
190                 for (final Node node : listTableNodes) {
191                         if (!(node instanceof Element))
192                                 continue;
193
194                         final Element element = (Element) node;
195                         final String tableName = element.getAttribute(ATTRIBUTE_NAME);
196
197                         // We first load the fields, relationships, etc:
198                         final TableInfo info = getTableInfo(tableName);
199                         if (info == null) {
200                                 continue;
201                         }
202
203                         // We then load the layouts afterwards, because they
204                         // need the fields and relationships:
205                         loadTableLayouts(element, info);
206
207                         tablesMap.put(info.getName(), info);
208                 }
209
210                 final Element nodeConnection = getElementByName(rootNode, NODE_CONNECTION);
211                 if (nodeConnection != null) {
212                         connectionServer = nodeConnection.getAttribute(ATTRIBUTE_CONNECTION_SERVER);
213                         connectionDatabase = nodeConnection.getAttribute(ATTRIBUTE_CONNECTION_DATABASE);
214                         connectionPort = getAttributeAsDecimal(nodeConnection, ATTRIBUTE_CONNECTION_PORT);
215                 }
216
217                 return true;
218         };
219
220         private Element getElementByName(final Element parentElement, final String tagName) {
221                 final List<Node> listNodes = getChildrenByTagName(parentElement, tagName);
222                 if (listNodes == null)
223                         return null;
224
225                 if (listNodes.size() == 0)
226                         return null;
227
228                 return (Element) listNodes.get(0);
229         }
230
231         private boolean getAttributeAsBoolean(final Element node, final String attributeName) {
232                 final String str = node.getAttribute(attributeName);
233                 if (str == null)
234                         return false;
235
236                 return str.equals("true");
237         }
238
239         /**
240          * @param elementFormatting
241          * @param aTTRIBUTE_DECIMAL_PLACES2
242          * @return
243          */
244         private int getAttributeAsDecimal(final Element node, final String attributeName) {
245                 final String str = node.getAttribute(attributeName);
246                 if (StringUtils.isEmpty(str))
247                         return 0;
248
249                 return Integer.valueOf(str);
250         }
251
252         /**
253          * Load a title and its translations.
254          * 
255          * @param node
256          *            The XML Element that may contain a title attribute and a trans_set of translations of the title.
257          * @param title
258          */
259         private void loadTitle(final Element node, final Translatable title) {
260                 title.setName(node.getAttribute(ATTRIBUTE_NAME));
261
262                 title.setTitleOriginal(node.getAttribute(ATTRIBUTE_TITLE));
263
264                 final Element nodeSet = getElementByName(node, NODE_TRANSLATIONS_SET);
265                 if (nodeSet == null) {
266                         return;
267                 }
268
269                 final List<Node> listNodes = getChildrenByTagName(nodeSet, NODE_TRANSLATIONS);
270                 if (listNodes == null)
271                         return;
272
273                 for (final Node transNode : listNodes) {
274                         if (!(transNode instanceof Element)) {
275                                 continue;
276                         }
277
278                         final Element element = (Element) transNode;
279
280                         final String locale = element.getAttribute(ATTRIBUTE_TRANSLATION_LOCALE);
281                         final String translatedTitle = element.getAttribute(ATTRIBUTE_TRANSLATION_TITLE);
282                         if (!StringUtils.isEmpty(locale) && !StringUtils.isEmpty(translatedTitle)) {
283                                 title.setTitle(translatedTitle, locale);
284                         }
285                 }
286         }
287
288         /**
289          * @param tableNode
290          * @return
291          */
292         private TableInfo loadTableNodeBasic(final Element tableNode) {
293                 final TableInfo info = new TableInfo();
294                 loadTitle(tableNode, info);
295                 final String tableName = info.getName();
296
297                 info.isDefault = getAttributeAsBoolean(tableNode, ATTRIBUTE_DEFAULT);
298                 info.isHidden = getAttributeAsBoolean(tableNode, ATTRIBUTE_HIDDEN);
299
300                 // These should be loaded before the fields, because the fields use them.
301                 final Element relationshipsNode = getElementByName(tableNode, NODE_RELATIONSHIPS);
302                 if (relationshipsNode != null) {
303                         final List<Node> listNodes = getChildrenByTagName(relationshipsNode, NODE_RELATIONSHIP);
304                         for (final Node node : listNodes) {
305                                 if (!(node instanceof Element)) {
306                                         continue;
307                                 }
308
309                                 final Element element = (Element) node;
310                                 final Relationship relationship = new Relationship();
311                                 loadTitle(element, relationship);
312                                 relationship.setFromTable(tableName);
313                                 relationship.setFromField(element.getAttribute(ATTRIBUTE_RELATIONSHIP_FROM_FIELD));
314                                 relationship.setToTable(element.getAttribute(ATTRIBUTE_RELATIONSHIP_TO_TABLE));
315                                 relationship.setToField(element.getAttribute(ATTRIBUTE_RELATIONSHIP_TO_FIELD));
316
317                                 info.relationshipsMap.put(relationship.getName(), relationship);
318                         }
319                 }
320
321                 final Element fieldsNode = getElementByName(tableNode, NODE_FIELDS);
322                 if (fieldsNode != null) {
323                         final List<Node> listNodes = getChildrenByTagName(fieldsNode, NODE_FIELD);
324                         for (final Node node : listNodes) {
325                                 if (!(node instanceof Element)) {
326                                         continue;
327                                 }
328
329                                 final Element element = (Element) node;
330                                 final Field field = new Field();
331                                 loadField(element, field);
332
333                                 info.fieldsMap.put(field.getName(), field);
334                         }
335                 }
336
337                 return info;
338         }
339
340         /**
341          * @param tableNode
342          * @param info
343          */
344         private void loadTableLayouts(final Element tableNode, final TableInfo info) {
345                 final String tableName = info.getName();
346
347                 final Element layoutsNode = getElementByName(tableNode, NODE_DATA_LAYOUTS);
348                 if (layoutsNode != null) {
349                         final List<Node> listNodes = getChildrenByTagName(layoutsNode, NODE_DATA_LAYOUT);
350                         for (final Node node : listNodes) {
351                                 if (!(node instanceof Element)) {
352                                         continue;
353                                 }
354
355                                 final Element element = (Element) node;
356                                 final String name = element.getAttribute("name");
357                                 final List<LayoutGroup> listLayoutGroups = loadLayoutNode(element, tableName);
358                                 if (name.equals(LAYOUT_NAME_DETAILS)) {
359                                         info.layoutGroupsDetails = listLayoutGroups;
360                                 } else if (name.equals(LAYOUT_NAME_LIST)) {
361                                         info.layoutGroupsList = listLayoutGroups;
362                                 } else {
363                                         Log.error("loadTableNode(): unexpected layout name: " + name);
364                                 }
365                         }
366                 }
367
368                 final Element reportsNode = getElementByName(tableNode, NODE_REPORTS);
369                 if (reportsNode != null) {
370                         final List<Node> listNodes = getChildrenByTagName(reportsNode, NODE_REPORT);
371                         for (final Node node : listNodes) {
372                                 if (!(node instanceof Element)) {
373                                         continue;
374                                 }
375
376                                 final Element element = (Element) node;
377                                 final Report report = new Report();
378                                 loadReport(element, report, tableName);
379
380                                 info.reportsMap.put(report.getName(), report);
381                         }
382                 }
383         }
384
385         /**
386          * @param node
387          * @return
388          */
389         private List<LayoutGroup> loadLayoutNode(final Element node, final String tableName) {
390                 if (node == null) {
391                         return null;
392                 }
393
394                 final List<LayoutGroup> result = new ArrayList<LayoutGroup>();
395
396                 final List<Node> listNodes = getChildrenByTagName(node, NODE_DATA_LAYOUT_GROUPS);
397                 for (final Node nodeGroups : listNodes) {
398                         if (!(nodeGroups instanceof Element)) {
399                                 continue;
400                         }
401
402                         final Element elementGroups = (Element) nodeGroups;
403
404                         final NodeList list = elementGroups.getChildNodes();
405                         final int num = list.getLength();
406                         for (int i = 0; i < num; i++) {
407                                 final Node nodeLayoutGroup = list.item(i);
408                                 if (nodeLayoutGroup == null) {
409                                         continue;
410                                 }
411
412                                 if (!(nodeLayoutGroup instanceof Element)) {
413                                         continue;
414                                 }
415
416                                 final Element element = (Element) nodeLayoutGroup;
417                                 final String tagName = element.getTagName();
418                                 if (tagName == NODE_DATA_LAYOUT_GROUP) {
419                                         final LayoutGroup group = new LayoutGroup();
420                                         loadDataLayoutGroup(element, group, tableName);
421                                         result.add(group);
422                                 } else if (tagName == NODE_DATA_LAYOUT_NOTEBOOK) {
423                                         final LayoutItemNotebook group = new LayoutItemNotebook();
424                                         loadDataLayoutGroup(element, group, tableName);
425                                         result.add(group);
426                                 } else if (tagName == NODE_DATA_LAYOUT_PORTAL) {
427                                         final LayoutItemPortal portal = new LayoutItemPortal();
428                                         loadDataLayoutPortal(element, portal, tableName);
429                                         result.add(portal);
430                                 }
431                         }
432                 }
433
434                 return result;
435         }
436
437         /**
438          * @param element
439          * @param tableName
440          * @param portal
441          */
442         private void loadUsesRelationship(final Element element, final String tableName, final UsesRelationship item) {
443                 if (element == null) {
444                         return;
445                 }
446
447                 if (item == null) {
448                         return;
449                 }
450
451                 final String relationshipName = element.getAttribute(ATTRIBUTE_RELATIONSHIP_NAME);
452                 Relationship relationship = null;
453                 if (!StringUtils.isEmpty(relationshipName)) {
454                         // std::cout << "  debug in : tableName=" << tableName << ", relationshipName=" << relationship_name <<
455                         // std::endl;
456                         relationship = getRelationship(tableName, relationshipName);
457                         item.setRelationship(relationship);
458
459                         if (relationship == null) {
460                                 Log.error("relationship not found: " + relationshipName + ", in table: " + tableName);
461                         }
462                 }
463
464                 final String relatedRelationshipName = element.getAttribute(ATTRIBUTE_RELATED_RELATIONSHIP_NAME);
465                 if (!StringUtils.isEmpty(relatedRelationshipName) && (relationship != null)) {
466                         final Relationship relatedRelationship = getRelationship(relationship.getToTable(), relatedRelationshipName);
467                         if (relatedRelationship == null) {
468                                 Log.error("related relationship not found in table=" + relationship.getToTable() + ",  name="
469                                                 + relatedRelationshipName);
470
471                                 item.setRelatedRelationship(relatedRelationship);
472                         }
473                 }
474         }
475
476         /**
477          * getElementsByTagName() is recursive, but we do not want that.
478          * 
479          * @param node
480          * @param
481          * @return
482          */
483         private List<Node> getChildrenByTagName(final Element parentNode, final String tagName) {
484                 final List<Node> result = new ArrayList<Node>();
485
486                 final NodeList list = parentNode.getElementsByTagName(tagName);
487                 final int num = list.getLength();
488                 for (int i = 0; i < num; i++) {
489                         final Node node = list.item(i);
490                         if (node == null) {
491                                 continue;
492                         }
493
494                         final Node itemParentNode = node.getParentNode();
495                         if (itemParentNode.equals(parentNode)) {
496                                 result.add(node);
497                         }
498                 }
499
500                 return result;
501         }
502
503         /**
504          * @param element
505          * @param group
506          */
507         private void loadDataLayoutGroup(final Element nodeGroup, final LayoutGroup group, final String tableName) {
508                 loadTitle(nodeGroup, group);
509                 
510                 group.setColumnCount( getAttributeAsDecimal(nodeGroup, ATTRIBUTE_LAYOUT_GROUP_COLUMNS_COUNT));
511
512                 final NodeList listNodes = nodeGroup.getChildNodes();
513                 final int num = listNodes.getLength();
514                 for (int i = 0; i < num; i++) {
515                         final Node node = listNodes.item(i);
516                         if (!(node instanceof Element))
517                                 continue;
518
519                         final Element element = (Element) node;
520                         final String tagName = element.getTagName();
521                         if (tagName == NODE_DATA_LAYOUT_GROUP) {
522                                 final LayoutGroup childGroup = new LayoutGroup();
523                                 loadDataLayoutGroup(element, childGroup, tableName);
524                                 group.addItem(childGroup);
525                         } else if (tagName == NODE_DATA_LAYOUT_NOTEBOOK) {
526                                 final LayoutItemNotebook childGroup = new LayoutItemNotebook();
527                                 loadDataLayoutGroup(element, childGroup, tableName);
528                                 group.addItem(childGroup);
529                         } else if (tagName == NODE_DATA_LAYOUT_PORTAL) {
530                                 final LayoutItemPortal childGroup = new LayoutItemPortal();
531                                 loadDataLayoutPortal(element, childGroup, tableName);
532                                 group.addItem(childGroup);
533                         } else if (element.getTagName() == NODE_DATA_LAYOUT_ITEM) {
534                                 final LayoutItemField item = new LayoutItemField();
535                                 loadDataLayoutItemField(element, item, tableName);
536                                 group.addItem(item);
537                         } else if (element.getTagName() == NODE_DATA_LAYOUT_ITEM_GROUPBY) {
538                                 final LayoutItemGroupBy item = new LayoutItemGroupBy();
539                                 loadDataLayoutItemGroupBy(element, item, tableName);
540                                 group.addItem(item);
541                         }
542                 }
543         }
544
545         /**
546          * @param element
547          * @param item
548          * @param tableName
549          */
550         private void loadDataLayoutItemGroupBy(final Element element, final LayoutItemGroupBy item, final String tableName) {
551                 loadDataLayoutGroup(element, item, tableName);
552
553                 final Element elementGroupBy = getElementByName(element, NODE_GROUPBY);
554                 if (elementGroupBy == null) {
555                         return;
556                 }
557
558                 final LayoutItemField fieldGroupBy = new LayoutItemField();
559                 loadDataLayoutItemField(elementGroupBy, fieldGroupBy, tableName);
560                 item.setFieldGroupBy(fieldGroupBy);
561
562                 final Element elementSecondaryFields = getElementByName(element, NODE_SECONDARY_FIELDS);
563                 if (elementSecondaryFields == null) {
564                         return;
565                 }
566
567                 final Element elementLayoutGroup = getElementByName(elementSecondaryFields, NODE_DATA_LAYOUT_GROUP);
568                 if (elementLayoutGroup != null) {
569                         final LayoutGroup secondaryLayoutGroup = new LayoutGroup();
570                         loadDataLayoutGroup(elementLayoutGroup, secondaryLayoutGroup, tableName);
571                         item.setSecondaryFields(secondaryLayoutGroup);
572                 }
573         }
574
575         /**
576          * @param element
577          * @param item
578          */
579         private void loadDataLayoutItemField(final Element element, final LayoutItemField item, final String tableName) {
580                 loadTitle(element, item);
581                 loadUsesRelationship(element, tableName, item);
582
583                 // Get the actual field:
584                 final String fieldName = item.getName();
585                 final String inTableName = item.getTableUsed(tableName);
586                 final Field field = getField(inTableName, fieldName);
587                 item.setFullFieldDetails(field);
588
589                 item.setUseDefaultFormatting(getAttributeAsBoolean(element, ATTRIBUTE_USE_DEFAULT_FORMATTING));
590
591                 final Element elementFormatting = getElementByName(element, NODE_FORMATTING);
592                 if (elementFormatting != null) {
593                         loadFormatting(elementFormatting, item.getFormatting());
594                 }
595         }
596
597         /**
598          * @param element
599          * @param childGroup
600          */
601         private void loadDataLayoutPortal(final Element element, final LayoutItemPortal portal, final String tableName) {
602                 loadUsesRelationship(element, tableName, portal);
603                 final String relatedTableName = portal.getTableUsed(tableName);
604                 loadDataLayoutGroup(element, portal, relatedTableName);
605
606                 final Element elementNavigation = getElementByName(element, NODE_DATA_LAYOUT_PORTAL_NAVIGATIONRELATIONSHIP);
607                 if (elementNavigation != null) {
608                         final String navigationTypeAsString = elementNavigation.getAttribute(ATTRIBUTE_PORTAL_NAVIGATION_TYPE);
609                         if (StringUtils.isEmpty(navigationTypeAsString)
610                                         || navigationTypeAsString == ATTRIBUTE_PORTAL_NAVIGATION_TYPE_AUTOMATIC) {
611                                 portal.setNavigationType(LayoutItemPortal.NavigationType.NAVIGATION_AUTOMATIC);
612                         } else if (navigationTypeAsString == ATTRIBUTE_PORTAL_NAVIGATION_TYPE_NONE) {
613                                 portal.setNavigationType(LayoutItemPortal.NavigationType.NAVIGATION_NONE);
614                         } else if (navigationTypeAsString == ATTRIBUTE_PORTAL_NAVIGATION_TYPE_SPECIFIC) {
615                                 // Read the specified relationship name:
616                                 final UsesRelationship relationshipNavigationSpecific = new UsesRelationshipImpl();
617                                 loadUsesRelationship(elementNavigation, relatedTableName, relationshipNavigationSpecific);
618                                 portal.setNavigationRelationshipSpecific(relationshipNavigationSpecific);
619                         }
620                 }
621
622         }
623
624         /**
625          * @param element
626          * @param field
627          */
628         private void loadField(final Element element, final Field field) {
629                 loadTitle(element, field);
630
631                 Field.GlomFieldType fieldType = Field.GlomFieldType.TYPE_INVALID;
632                 final String fieldTypeStr = element.getAttribute(ATTRIBUTE_FIELD_TYPE);
633                 if (!StringUtils.isEmpty(fieldTypeStr)) {
634                         if (fieldTypeStr.equals("Boolean")) {
635                                 fieldType = Field.GlomFieldType.TYPE_BOOLEAN;
636                         } else if (fieldTypeStr.equals("Date")) {
637                                 fieldType = Field.GlomFieldType.TYPE_DATE;
638                         } else if (fieldTypeStr.equals("Image")) {
639                                 fieldType = Field.GlomFieldType.TYPE_IMAGE;
640                         } else if (fieldTypeStr.equals("Number")) {
641                                 fieldType = Field.GlomFieldType.TYPE_NUMERIC;
642                         } else if (fieldTypeStr.equals("Text")) {
643                                 fieldType = Field.GlomFieldType.TYPE_TEXT;
644                         } else if (fieldTypeStr.equals("Time")) {
645                                 fieldType = Field.GlomFieldType.TYPE_TIME;
646                         }
647                 }
648
649                 field.setGlomFieldType(fieldType);
650
651                 field.setPrimaryKey(getAttributeAsBoolean(element, ATTRIBUTE_PRIMARY_KEY));
652                 loadTitle(element, field);
653
654                 final Element elementFormatting = getElementByName(element, NODE_FORMATTING);
655                 if (elementFormatting != null) {
656                         loadFormatting(elementFormatting, field.getFormatting());
657                 }
658         }
659
660         /**
661          * @param elementFormatting
662          * @param formatting
663          */
664         private void loadFormatting(final Element elementFormatting, final Formatting formatting) {
665                 if (elementFormatting == null)
666                         return;
667
668                 if (formatting == null)
669                         return;
670
671                 // formatting.setTextFormatMultiline(getAttributeAsBoolean(elementFormatting, ATTRIBUTE_TEXT_FORMAT_MULTILINE));
672
673                 final NumericFormat numericFormatting = formatting.getNumericFormat();
674                 if (numericFormatting != null) {
675                         numericFormatting.setUseThousandsSeparator(getAttributeAsBoolean(elementFormatting,
676                                         ATTRIBUTE_USE_THOUSANDS_SEPARATOR));
677                         numericFormatting.setDecimalPlaces(getAttributeAsDecimal(elementFormatting, ATTRIBUTE_DECIMAL_PLACES));
678                 }
679
680         }
681
682         /**
683          * @param element
684          * @param reportNode
685          */
686         private void loadReport(final Element element, final Report report, final String tableName) {
687                 report.setName(element.getAttribute(ATTRIBUTE_NAME));
688                 loadTitle(element, report);
689
690                 final List<LayoutGroup> listLayoutGroups = loadLayoutNode(element, tableName);
691
692                 // A report can actually only have one LayoutGroup,
693                 // though it uses the same XML structure as List and Details layouts,
694                 // which (wrongly) suggests that it can have more than one group.
695                 LayoutGroup layoutGroup = null;
696                 if (!listLayoutGroups.isEmpty()) {
697                         layoutGroup = listLayoutGroups.get(0);
698                 }
699
700                 report.setLayoutGroup(layoutGroup);
701         }
702
703         private TableInfo getTableInfo(final String tableName) {
704                 return tablesMap.get(tableName);
705         }
706
707         public enum HostingMode {
708                 HOSTING_MODE_POSTGRES_CENTRAL, HOSTING_MODE_POSTGRES_SELF, HOSTING_MODE_SQLITE
709         };
710
711         public String getDatabaseTitle(final String locale) {
712                 return databaseTitle.getTitle(locale);
713         }
714
715         public String getDatabaseTitleOriginal() {
716                 return databaseTitle.getTitleOriginal();
717         }
718
719         public List<String> getTranslationAvailableLocales() {
720                 return translationAvailableLocales;
721         }
722
723         public Document.HostingMode getHostingMode() {
724                 return HostingMode.HOSTING_MODE_POSTGRES_CENTRAL; // TODO
725         }
726
727         public String getConnectionServer() {
728                 return connectionServer;
729         }
730
731         public long getConnectionPort() {
732                 return connectionPort;
733         }
734
735         public String getConnectionDatabase() {
736                 return connectionDatabase;
737         }
738
739         public List<String> getTableNames() {
740                 // TODO: Return a Set?
741                 return new ArrayList<String>(tablesMap.keySet());
742         }
743
744         public boolean getTableIsHidden(final String tableName) {
745                 final TableInfo info = getTableInfo(tableName);
746                 if (info == null) {
747                         return false;
748                 }
749
750                 return info.isHidden;
751         }
752
753         public String getTableTitle(final String tableName, final String locale) {
754                 final TableInfo info = getTableInfo(tableName);
755                 if (info == null) {
756                         return "";
757                 }
758
759                 return info.getTitle(locale);
760         }
761
762         public String getDefaultTable() {
763                 for (final TableInfo info : tablesMap.values()) {
764                         if (info.isDefault) {
765                                 return info.getName();
766                         }
767                 }
768
769                 return "";
770         }
771
772         public boolean getTableIsKnown(final String tableName) {
773                 final TableInfo info = getTableInfo(tableName);
774                 if (info == null) {
775                         return false;
776                 }
777
778                 return true;
779         }
780
781         public List<Field> getTableFields(final String tableName) {
782                 final TableInfo info = getTableInfo(tableName);
783                 if (info == null)
784                         return null;
785
786                 return new ArrayList<Field>(info.fieldsMap.values());
787         }
788
789         public Field getField(final String tableName, final String strFieldName) {
790                 final TableInfo info = getTableInfo(tableName);
791                 if (info == null)
792                         return null;
793
794                 return info.fieldsMap.get(strFieldName);
795         }
796
797         public List<LayoutGroup> getDataLayoutGroups(final String layoutName, final String parentTableName) {
798                 final TableInfo info = getTableInfo(parentTableName);
799                 if (info == null)
800                         return new ArrayList<LayoutGroup>();
801
802                 if (layoutName == LAYOUT_NAME_DETAILS) {
803                         return info.layoutGroupsDetails;
804                 } else if (layoutName == LAYOUT_NAME_LIST) {
805                         return info.layoutGroupsList;
806                 } else {
807                         return new ArrayList<LayoutGroup>();
808                 }
809         }
810
811         public List<String> getReportNames(final String tableName) {
812                 final TableInfo info = getTableInfo(tableName);
813                 if (info == null)
814                         return new ArrayList<String>();
815
816                 return new ArrayList<String>(info.reportsMap.keySet());
817         }
818
819         public Report getReport(final String tableName, final String reportName) {
820                 final TableInfo info = getTableInfo(tableName);
821                 if (info == null)
822                         return null;
823
824                 return info.reportsMap.get(reportName);
825         }
826
827         /**
828          * @param tableName
829          * @param field
830          * @return
831          */
832         public Relationship getFieldUsedInRelationshipToOne(final String tableName, final LayoutItemField layoutField) {
833
834                 if (layoutField == null) {
835                         Log.error("layoutField was null");
836                         return null;
837                 }
838
839                 Relationship result = null;
840
841                 final String tableUsed = layoutField.getTableUsed(tableName);
842                 final TableInfo info = getTableInfo(tableUsed);
843                 if (info == null) {
844                         // This table is special. We would not create a relationship to it using a field:
845                         // if(tableUsed == GLOM_STANDARD_TABLE_PREFS_TABLE_NAME)
846                         // return result;
847
848                         Log.error("table not found: " + tableUsed);
849                         return null;
850                 }
851
852                 // Look at each relationship:
853                 final String fieldName = layoutField.getName();
854                 for (final Relationship relationship : info.relationshipsMap.values()) {
855                         if (relationship != null) {
856                                 // If the relationship uses the field
857                                 if (relationship.getFromField() == fieldName) {
858                                         // if the to_table is not hidden:
859                                         if (!getTableIsHidden(relationship.getToTable())) {
860                                                 // TODO_Performance: The use of this convenience method means we get the full relationship
861                                                 // information again:
862                                                 if (getRelationshipIsToOne(tableName, relationship.getName())) {
863                                                         result = relationship;
864                                                 }
865                                         }
866                                 }
867                         }
868                 }
869
870                 return result;
871         }
872
873         /**
874          * @param tableName
875          * @param relationshipName
876          * @return
877          */
878         private boolean getRelationshipIsToOne(final String tableName, final String relationshipName) {
879                 final Relationship relationship = getRelationship(tableName, relationshipName);
880                 if (relationship != null) {
881                         final Field fieldTo = getField(relationship.getToTable(), relationship.getToField());
882                         if (fieldTo != null) {
883                                 return (fieldTo.getPrimaryKey() || fieldTo.getUniqueKey());
884                         }
885                 }
886
887                 return false;
888         }
889
890         /**
891          * @param tableName
892          * @param relationshipName
893          * @return
894          */
895         private Relationship getRelationship(final String tableName, final String relationshipName) {
896                 final TableInfo info = getTableInfo(tableName);
897                 if (info == null) {
898                         Log.error("table not found: " + tableName);
899                         return null;
900                 }
901
902                 return info.relationshipsMap.get(relationshipName);
903         }
904
905         public class TableToViewDetails {
906                 public String tableName;
907                 public UsesRelationship usesRelationship;
908         }
909
910         /**
911          * @param tableName
912          *            Output parameter
913          * @param relationship
914          * @param portal
915          *            TODO
916          */
917         public TableToViewDetails getPortalSuitableTableToViewDetails(LayoutItemPortal portal) {
918                 UsesRelationship navigationRelationship = null;
919
920                 // Check whether a relationship was specified:
921                 if (portal.getNavigationType() == NavigationType.NAVIGATION_AUTOMATIC) {
922                         navigationRelationship = getPortalNavigationRelationshipAutomatic(portal);
923                 } else {
924                         navigationRelationship = portal.getNavigationRelationshipSpecific();
925                 }
926
927                 // Get the navigation table name from the chosen relationship:
928                 String directlyRelatedTableName = portal.getTableUsed("" /* not relevant */);
929
930                 // The navigation_table_name (and therefore, the table_name output parameter,
931                 // as well) stays empty if the navrel type was set to none.
932                 String navigationTableName = null;
933                 if (navigationRelationship != null) {
934                         navigationTableName = navigationRelationship.getTableUsed(directlyRelatedTableName);
935                 } else if (portal.getNavigationType() != NavigationType.NAVIGATION_NONE) {
936                         // An empty result from get_portal_navigation_relationship_automatic() or
937                         // get_navigation_relationship_specific() means we should use the directly related table:
938                         navigationTableName = directlyRelatedTableName;
939                 }
940
941                 if (StringUtils.isEmpty(navigationTableName)) {
942                         return null;
943                 }
944
945                 if (this == null) {
946                         Log.error("document is null.");
947                         return null;
948                 }
949
950                 if (getTableIsHidden(navigationTableName)) {
951                         Log.error("navigation_table_name indicates a hidden table: " + navigationTableName);
952                         return null;
953                 }
954
955                 TableToViewDetails result = new TableToViewDetails();
956                 result.tableName = navigationTableName;
957                 result.usesRelationship = navigationRelationship;
958                 return result;
959         }
960
961         /**
962          * @param portal
963          *            TODO
964          * @return
965          */
966         private UsesRelationship getPortalNavigationRelationshipAutomatic(LayoutItemPortal portal) {
967                 if (this == null) {
968                         return null;
969                 }
970
971                 // If the related table is not hidden then we can just navigate to that:
972                 final String direct_related_table_name = portal.getTableUsed("" /* parent table - not relevant */);
973                 if (!getTableIsHidden(direct_related_table_name)) {
974                         // Non-hidden tables can just be shown directly. Navigate to it:
975                         return null;
976                 } else {
977                         // If the related table is hidden,
978                         // then find a suitable related non-hidden table by finding the first layout field that mentions one:
979                         final LayoutItemField field = getPortalFieldIsFromNonHiddenRelatedRecord(portal);
980                         if (field != null) {
981                                 return field; // Returns the UsesRelationship base part. (A relationship belonging to the portal's
982                                                                 // related table.)
983                         } else {
984                                 // Instead, find a key field that's used in a relationship,
985                                 // and pretend that we are showing the to field as a related field:
986                                 final Relationship fieldIndentifies = getPortalFieldIdentifiesNonHiddenRelatedRecord(portal);
987                                 if (fieldIndentifies != null) {
988                                         UsesRelationship result = new UsesRelationshipImpl();
989                                         result.setRelationship(fieldIndentifies);
990                                         return result;
991                                 }
992                         }
993                 }
994
995                 // There was no suitable related table to show:
996                 return null;
997         }
998
999         /**
1000          * @param portal
1001          *            TODO
1002          * @return
1003          */
1004         private LayoutItemField getPortalFieldIsFromNonHiddenRelatedRecord(LayoutItemPortal portal) {
1005                 // Find the first field that is from a non-hidden related table.
1006
1007                 if (this == null) {
1008                         return null;
1009                 }
1010
1011                 LayoutItemField result = null;
1012
1013                 final String parent_table_name = portal.getTableUsed("" /* parent table - not relevant */);
1014
1015                 final List<LayoutItem> items = portal.getItems();
1016                 for (LayoutItem item : items) {
1017                         if (item instanceof LayoutItemField) {
1018                                 LayoutItemField field = (LayoutItemField) item;
1019                                 if (field.getHasRelationshipName()) {
1020                                         final String table_name = field.getTableUsed(parent_table_name);
1021                                         if (!(getTableIsHidden(table_name)))
1022                                                 return field;
1023                                 }
1024                         }
1025                 }
1026
1027                 return result;
1028         }
1029
1030         /**
1031          * @param used_in_relationship
1032          * @param portal
1033          *            TODO
1034          * @return
1035          */
1036         private Relationship getPortalFieldIdentifiesNonHiddenRelatedRecord(LayoutItemPortal portal) {
1037                 // Find the first field that is from a non-hidden related table.
1038
1039                 if (this == null) {
1040                         Log.error("document is null");
1041                         return null;
1042                 }
1043
1044                 final String parent_table_name = portal.getTableUsed("" /* parent table - not relevant */);
1045
1046                 List<LayoutItem> items = portal.getItems();
1047                 for (LayoutItem item : items) {
1048                         if (item instanceof LayoutItemField) {
1049                                 LayoutItemField field = (LayoutItemField) item;
1050                                 if (field.getHasRelationshipName()) {
1051                                         final Relationship relationship = getFieldUsedInRelationshipToOne(parent_table_name, field);
1052                                         if (relationship != null) {
1053                                                 final String table_name = relationship.getToTable();
1054                                                 if (!StringUtils.isEmpty(table_name)) {
1055                                                         if (!(getTableIsHidden(table_name))) {
1056                                                                 return relationship;
1057                                                         }
1058                                                 }
1059                                         }
1060                                 }
1061                         }
1062                 }
1063
1064                 return null;
1065         }
1066 }