Document: Interpret no group column count as 1.
[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                 // Read the column count:
511                 int columnCount = getAttributeAsDecimal(nodeGroup, ATTRIBUTE_LAYOUT_GROUP_COLUMNS_COUNT);
512                 if (columnCount < 1) {
513                         columnCount = 1; // 0 is a useless default.
514                 }
515                 group.setColumnCount(columnCount);
516
517                 // Get the child items:
518                 final NodeList listNodes = nodeGroup.getChildNodes();
519                 final int num = listNodes.getLength();
520                 for (int i = 0; i < num; i++) {
521                         final Node node = listNodes.item(i);
522                         if (!(node instanceof Element))
523                                 continue;
524
525                         final Element element = (Element) node;
526                         final String tagName = element.getTagName();
527                         if (tagName == NODE_DATA_LAYOUT_GROUP) {
528                                 final LayoutGroup childGroup = new LayoutGroup();
529                                 loadDataLayoutGroup(element, childGroup, tableName);
530                                 group.addItem(childGroup);
531                         } else if (tagName == NODE_DATA_LAYOUT_NOTEBOOK) {
532                                 final LayoutItemNotebook childGroup = new LayoutItemNotebook();
533                                 loadDataLayoutGroup(element, childGroup, tableName);
534                                 group.addItem(childGroup);
535                         } else if (tagName == NODE_DATA_LAYOUT_PORTAL) {
536                                 final LayoutItemPortal childGroup = new LayoutItemPortal();
537                                 loadDataLayoutPortal(element, childGroup, tableName);
538                                 group.addItem(childGroup);
539                         } else if (element.getTagName() == NODE_DATA_LAYOUT_ITEM) {
540                                 final LayoutItemField item = new LayoutItemField();
541                                 loadDataLayoutItemField(element, item, tableName);
542                                 group.addItem(item);
543                         } else if (element.getTagName() == NODE_DATA_LAYOUT_ITEM_GROUPBY) {
544                                 final LayoutItemGroupBy item = new LayoutItemGroupBy();
545                                 loadDataLayoutItemGroupBy(element, item, tableName);
546                                 group.addItem(item);
547                         }
548                 }
549         }
550
551         /**
552          * @param element
553          * @param item
554          * @param tableName
555          */
556         private void loadDataLayoutItemGroupBy(final Element element, final LayoutItemGroupBy item, final String tableName) {
557                 loadDataLayoutGroup(element, item, tableName);
558
559                 final Element elementGroupBy = getElementByName(element, NODE_GROUPBY);
560                 if (elementGroupBy == null) {
561                         return;
562                 }
563
564                 final LayoutItemField fieldGroupBy = new LayoutItemField();
565                 loadDataLayoutItemField(elementGroupBy, fieldGroupBy, tableName);
566                 item.setFieldGroupBy(fieldGroupBy);
567
568                 final Element elementSecondaryFields = getElementByName(element, NODE_SECONDARY_FIELDS);
569                 if (elementSecondaryFields == null) {
570                         return;
571                 }
572
573                 final Element elementLayoutGroup = getElementByName(elementSecondaryFields, NODE_DATA_LAYOUT_GROUP);
574                 if (elementLayoutGroup != null) {
575                         final LayoutGroup secondaryLayoutGroup = new LayoutGroup();
576                         loadDataLayoutGroup(elementLayoutGroup, secondaryLayoutGroup, tableName);
577                         item.setSecondaryFields(secondaryLayoutGroup);
578                 }
579         }
580
581         /**
582          * @param element
583          * @param item
584          */
585         private void loadDataLayoutItemField(final Element element, final LayoutItemField item, final String tableName) {
586                 loadTitle(element, item);
587                 loadUsesRelationship(element, tableName, item);
588
589                 // Get the actual field:
590                 final String fieldName = item.getName();
591                 final String inTableName = item.getTableUsed(tableName);
592                 final Field field = getField(inTableName, fieldName);
593                 item.setFullFieldDetails(field);
594
595                 item.setUseDefaultFormatting(getAttributeAsBoolean(element, ATTRIBUTE_USE_DEFAULT_FORMATTING));
596
597                 final Element elementFormatting = getElementByName(element, NODE_FORMATTING);
598                 if (elementFormatting != null) {
599                         loadFormatting(elementFormatting, item.getFormatting());
600                 }
601         }
602
603         /**
604          * @param element
605          * @param childGroup
606          */
607         private void loadDataLayoutPortal(final Element element, final LayoutItemPortal portal, final String tableName) {
608                 loadUsesRelationship(element, tableName, portal);
609                 final String relatedTableName = portal.getTableUsed(tableName);
610                 loadDataLayoutGroup(element, portal, relatedTableName);
611
612                 final Element elementNavigation = getElementByName(element, NODE_DATA_LAYOUT_PORTAL_NAVIGATIONRELATIONSHIP);
613                 if (elementNavigation != null) {
614                         final String navigationTypeAsString = elementNavigation.getAttribute(ATTRIBUTE_PORTAL_NAVIGATION_TYPE);
615                         if (StringUtils.isEmpty(navigationTypeAsString)
616                                         || navigationTypeAsString == ATTRIBUTE_PORTAL_NAVIGATION_TYPE_AUTOMATIC) {
617                                 portal.setNavigationType(LayoutItemPortal.NavigationType.NAVIGATION_AUTOMATIC);
618                         } else if (navigationTypeAsString == ATTRIBUTE_PORTAL_NAVIGATION_TYPE_NONE) {
619                                 portal.setNavigationType(LayoutItemPortal.NavigationType.NAVIGATION_NONE);
620                         } else if (navigationTypeAsString == ATTRIBUTE_PORTAL_NAVIGATION_TYPE_SPECIFIC) {
621                                 // Read the specified relationship name:
622                                 final UsesRelationship relationshipNavigationSpecific = new UsesRelationshipImpl();
623                                 loadUsesRelationship(elementNavigation, relatedTableName, relationshipNavigationSpecific);
624                                 portal.setNavigationRelationshipSpecific(relationshipNavigationSpecific);
625                         }
626                 }
627
628         }
629
630         /**
631          * @param element
632          * @param field
633          */
634         private void loadField(final Element element, final Field field) {
635                 loadTitle(element, field);
636
637                 Field.GlomFieldType fieldType = Field.GlomFieldType.TYPE_INVALID;
638                 final String fieldTypeStr = element.getAttribute(ATTRIBUTE_FIELD_TYPE);
639                 if (!StringUtils.isEmpty(fieldTypeStr)) {
640                         if (fieldTypeStr.equals("Boolean")) {
641                                 fieldType = Field.GlomFieldType.TYPE_BOOLEAN;
642                         } else if (fieldTypeStr.equals("Date")) {
643                                 fieldType = Field.GlomFieldType.TYPE_DATE;
644                         } else if (fieldTypeStr.equals("Image")) {
645                                 fieldType = Field.GlomFieldType.TYPE_IMAGE;
646                         } else if (fieldTypeStr.equals("Number")) {
647                                 fieldType = Field.GlomFieldType.TYPE_NUMERIC;
648                         } else if (fieldTypeStr.equals("Text")) {
649                                 fieldType = Field.GlomFieldType.TYPE_TEXT;
650                         } else if (fieldTypeStr.equals("Time")) {
651                                 fieldType = Field.GlomFieldType.TYPE_TIME;
652                         }
653                 }
654
655                 field.setGlomFieldType(fieldType);
656
657                 field.setPrimaryKey(getAttributeAsBoolean(element, ATTRIBUTE_PRIMARY_KEY));
658                 loadTitle(element, field);
659
660                 final Element elementFormatting = getElementByName(element, NODE_FORMATTING);
661                 if (elementFormatting != null) {
662                         loadFormatting(elementFormatting, field.getFormatting());
663                 }
664         }
665
666         /**
667          * @param elementFormatting
668          * @param formatting
669          */
670         private void loadFormatting(final Element elementFormatting, final Formatting formatting) {
671                 if (elementFormatting == null)
672                         return;
673
674                 if (formatting == null)
675                         return;
676
677                 // formatting.setTextFormatMultiline(getAttributeAsBoolean(elementFormatting, ATTRIBUTE_TEXT_FORMAT_MULTILINE));
678
679                 final NumericFormat numericFormatting = formatting.getNumericFormat();
680                 if (numericFormatting != null) {
681                         numericFormatting.setUseThousandsSeparator(getAttributeAsBoolean(elementFormatting,
682                                         ATTRIBUTE_USE_THOUSANDS_SEPARATOR));
683                         numericFormatting.setDecimalPlaces(getAttributeAsDecimal(elementFormatting, ATTRIBUTE_DECIMAL_PLACES));
684                 }
685
686         }
687
688         /**
689          * @param element
690          * @param reportNode
691          */
692         private void loadReport(final Element element, final Report report, final String tableName) {
693                 report.setName(element.getAttribute(ATTRIBUTE_NAME));
694                 loadTitle(element, report);
695
696                 final List<LayoutGroup> listLayoutGroups = loadLayoutNode(element, tableName);
697
698                 // A report can actually only have one LayoutGroup,
699                 // though it uses the same XML structure as List and Details layouts,
700                 // which (wrongly) suggests that it can have more than one group.
701                 LayoutGroup layoutGroup = null;
702                 if (!listLayoutGroups.isEmpty()) {
703                         layoutGroup = listLayoutGroups.get(0);
704                 }
705
706                 report.setLayoutGroup(layoutGroup);
707         }
708
709         private TableInfo getTableInfo(final String tableName) {
710                 return tablesMap.get(tableName);
711         }
712
713         public enum HostingMode {
714                 HOSTING_MODE_POSTGRES_CENTRAL, HOSTING_MODE_POSTGRES_SELF, HOSTING_MODE_SQLITE
715         };
716
717         public String getDatabaseTitle(final String locale) {
718                 return databaseTitle.getTitle(locale);
719         }
720
721         public String getDatabaseTitleOriginal() {
722                 return databaseTitle.getTitleOriginal();
723         }
724
725         public List<String> getTranslationAvailableLocales() {
726                 return translationAvailableLocales;
727         }
728
729         public Document.HostingMode getHostingMode() {
730                 return HostingMode.HOSTING_MODE_POSTGRES_CENTRAL; // TODO
731         }
732
733         public String getConnectionServer() {
734                 return connectionServer;
735         }
736
737         public long getConnectionPort() {
738                 return connectionPort;
739         }
740
741         public String getConnectionDatabase() {
742                 return connectionDatabase;
743         }
744
745         public List<String> getTableNames() {
746                 // TODO: Return a Set?
747                 return new ArrayList<String>(tablesMap.keySet());
748         }
749
750         public boolean getTableIsHidden(final String tableName) {
751                 final TableInfo info = getTableInfo(tableName);
752                 if (info == null) {
753                         return false;
754                 }
755
756                 return info.isHidden;
757         }
758
759         public String getTableTitle(final String tableName, final String locale) {
760                 final TableInfo info = getTableInfo(tableName);
761                 if (info == null) {
762                         return "";
763                 }
764
765                 return info.getTitle(locale);
766         }
767
768         public String getDefaultTable() {
769                 for (final TableInfo info : tablesMap.values()) {
770                         if (info.isDefault) {
771                                 return info.getName();
772                         }
773                 }
774
775                 return "";
776         }
777
778         public boolean getTableIsKnown(final String tableName) {
779                 final TableInfo info = getTableInfo(tableName);
780                 if (info == null) {
781                         return false;
782                 }
783
784                 return true;
785         }
786
787         public List<Field> getTableFields(final String tableName) {
788                 final TableInfo info = getTableInfo(tableName);
789                 if (info == null)
790                         return null;
791
792                 return new ArrayList<Field>(info.fieldsMap.values());
793         }
794
795         public Field getField(final String tableName, final String strFieldName) {
796                 final TableInfo info = getTableInfo(tableName);
797                 if (info == null)
798                         return null;
799
800                 return info.fieldsMap.get(strFieldName);
801         }
802
803         public List<LayoutGroup> getDataLayoutGroups(final String layoutName, final String parentTableName) {
804                 final TableInfo info = getTableInfo(parentTableName);
805                 if (info == null)
806                         return new ArrayList<LayoutGroup>();
807
808                 if (layoutName == LAYOUT_NAME_DETAILS) {
809                         return info.layoutGroupsDetails;
810                 } else if (layoutName == LAYOUT_NAME_LIST) {
811                         return info.layoutGroupsList;
812                 } else {
813                         return new ArrayList<LayoutGroup>();
814                 }
815         }
816
817         public List<String> getReportNames(final String tableName) {
818                 final TableInfo info = getTableInfo(tableName);
819                 if (info == null)
820                         return new ArrayList<String>();
821
822                 return new ArrayList<String>(info.reportsMap.keySet());
823         }
824
825         public Report getReport(final String tableName, final String reportName) {
826                 final TableInfo info = getTableInfo(tableName);
827                 if (info == null)
828                         return null;
829
830                 return info.reportsMap.get(reportName);
831         }
832
833         /**
834          * @param tableName
835          * @param field
836          * @return
837          */
838         public Relationship getFieldUsedInRelationshipToOne(final String tableName, final LayoutItemField layoutField) {
839
840                 if (layoutField == null) {
841                         Log.error("layoutField was null");
842                         return null;
843                 }
844
845                 Relationship result = null;
846
847                 final String tableUsed = layoutField.getTableUsed(tableName);
848                 final TableInfo info = getTableInfo(tableUsed);
849                 if (info == null) {
850                         // This table is special. We would not create a relationship to it using a field:
851                         // if(tableUsed == GLOM_STANDARD_TABLE_PREFS_TABLE_NAME)
852                         // return result;
853
854                         Log.error("table not found: " + tableUsed);
855                         return null;
856                 }
857
858                 // Look at each relationship:
859                 final String fieldName = layoutField.getName();
860                 for (final Relationship relationship : info.relationshipsMap.values()) {
861                         if (relationship != null) {
862                                 // If the relationship uses the field
863                                 if (relationship.getFromField() == fieldName) {
864                                         // if the to_table is not hidden:
865                                         if (!getTableIsHidden(relationship.getToTable())) {
866                                                 // TODO_Performance: The use of this convenience method means we get the full relationship
867                                                 // information again:
868                                                 if (getRelationshipIsToOne(tableName, relationship.getName())) {
869                                                         result = relationship;
870                                                 }
871                                         }
872                                 }
873                         }
874                 }
875
876                 return result;
877         }
878
879         /**
880          * @param tableName
881          * @param relationshipName
882          * @return
883          */
884         private boolean getRelationshipIsToOne(final String tableName, final String relationshipName) {
885                 final Relationship relationship = getRelationship(tableName, relationshipName);
886                 if (relationship != null) {
887                         final Field fieldTo = getField(relationship.getToTable(), relationship.getToField());
888                         if (fieldTo != null) {
889                                 return (fieldTo.getPrimaryKey() || fieldTo.getUniqueKey());
890                         }
891                 }
892
893                 return false;
894         }
895
896         /**
897          * @param tableName
898          * @param relationshipName
899          * @return
900          */
901         private Relationship getRelationship(final String tableName, final String relationshipName) {
902                 final TableInfo info = getTableInfo(tableName);
903                 if (info == null) {
904                         Log.error("table not found: " + tableName);
905                         return null;
906                 }
907
908                 return info.relationshipsMap.get(relationshipName);
909         }
910
911         public class TableToViewDetails {
912                 public String tableName;
913                 public UsesRelationship usesRelationship;
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 Relationship fieldIndentifies = getPortalFieldIdentifiesNonHiddenRelatedRecord(portal);
993                                 if (fieldIndentifies != null) {
994                                         UsesRelationship result = new UsesRelationshipImpl();
995                                         result.setRelationship(fieldIndentifies);
996                                         return result;
997                                 }
998                         }
999                 }
1000
1001                 // There was no suitable related table to show:
1002                 return null;
1003         }
1004
1005         /**
1006          * @param portal
1007          *            TODO
1008          * @return
1009          */
1010         private LayoutItemField getPortalFieldIsFromNonHiddenRelatedRecord(LayoutItemPortal portal) {
1011                 // Find the first field that is from a non-hidden related table.
1012
1013                 if (this == null) {
1014                         return null;
1015                 }
1016
1017                 LayoutItemField result = null;
1018
1019                 final String parent_table_name = portal.getTableUsed("" /* parent table - not relevant */);
1020
1021                 final List<LayoutItem> items = portal.getItems();
1022                 for (LayoutItem item : items) {
1023                         if (item instanceof LayoutItemField) {
1024                                 LayoutItemField field = (LayoutItemField) item;
1025                                 if (field.getHasRelationshipName()) {
1026                                         final String table_name = field.getTableUsed(parent_table_name);
1027                                         if (!(getTableIsHidden(table_name)))
1028                                                 return field;
1029                                 }
1030                         }
1031                 }
1032
1033                 return result;
1034         }
1035
1036         /**
1037          * @param used_in_relationship
1038          * @param portal
1039          *            TODO
1040          * @return
1041          */
1042         private Relationship getPortalFieldIdentifiesNonHiddenRelatedRecord(LayoutItemPortal portal) {
1043                 // Find the first field that is from a non-hidden related table.
1044
1045                 if (this == null) {
1046                         Log.error("document is null");
1047                         return null;
1048                 }
1049
1050                 final String parent_table_name = portal.getTableUsed("" /* parent table - not relevant */);
1051
1052                 List<LayoutItem> items = portal.getItems();
1053                 for (LayoutItem item : items) {
1054                         if (item instanceof LayoutItemField) {
1055                                 LayoutItemField field = (LayoutItemField) item;
1056                                 if (field.getHasRelationshipName()) {
1057                                         final Relationship relationship = getFieldUsedInRelationshipToOne(parent_table_name, field);
1058                                         if (relationship != null) {
1059                                                 final String table_name = relationship.getToTable();
1060                                                 if (!StringUtils.isEmpty(table_name)) {
1061                                                         if (!(getTableIsHidden(table_name))) {
1062                                                                 return relationship;
1063                                                         }
1064                                                 }
1065                                         }
1066                                 }
1067                         }
1068                 }
1069
1070                 return null;
1071         }
1072 }