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