LayoutItemPortal: Move use of Document.
[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 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 NODE_DATA_LAYOUT_NOTEBOOK = "data_layout_notebook";
115         private static final String NODE_DATA_LAYOUT_PORTAL = "data_layout_portal";
116         private static final String NODE_DATA_LAYOUT_PORTAL_NAVIGATIONRELATIONSHIP = "portal_navigation_relationship";
117         private static final String ATTRIBUTE_PORTAL_NAVIGATION_TYPE = "navigation_type";
118         private static final String ATTRIBUTE_PORTAL_NAVIGATION_TYPE_AUTOMATIC = "automatic";
119         private static final String ATTRIBUTE_PORTAL_NAVIGATION_TYPE_SPECIFIC = "specific";
120         private static final String ATTRIBUTE_PORTAL_NAVIGATION_TYPE_NONE = "none";
121         private static final String ATTRIBUTE_RELATIONSHIP_NAME = "relationship";
122         private static final String ATTRIBUTE_RELATED_RELATIONSHIP_NAME = "related_relationship";
123         private static final String NODE_DATA_LAYOUT_ITEM = "data_layout_item";
124         private static final String NODE_DATA_LAYOUT_ITEM_GROUPBY = "data_layout_item_groupby";
125         private static final String NODE_GROUPBY = "groupby";
126         private static final String NODE_SECONDARY_FIELDS = "secondary_fields";
127         private static final String ATTRIBUTE_USE_DEFAULT_FORMATTING = "use_default_formatting";
128         private static final String LAYOUT_NAME_DETAILS = "details";
129         private static final String LAYOUT_NAME_LIST = "list";
130
131         public void setFileURI(final String fileURI) {
132                 this.fileURI = fileURI;
133         }
134
135         public String getFileURI() {
136                 return fileURI;
137         }
138
139         // TODO: Make sure these have the correct values.
140         public enum LoadFailureCodes {
141                 LOAD_FAILURE_CODE_NONE, LOAD_FAILURE_CODE_NOT_FOUND, LOAD_FAILURE_CODE_FILE_VERSION_TOO_NEW
142         };
143
144         public boolean load(final int failure_code) {
145                 final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
146                 DocumentBuilder documentBuilder;
147                 try {
148                         documentBuilder = dbf.newDocumentBuilder();
149                 } catch (final ParserConfigurationException e) {
150                         // TODO Auto-generated catch block
151                         e.printStackTrace();
152                         return false;
153                 }
154
155                 try {
156                         xmlDocument = documentBuilder.parse(fileURI);
157                 } catch (final SAXException e) {
158                         // TODO Auto-generated catch block
159                         e.printStackTrace();
160                         return false;
161                 } catch (final IOException e) {
162                         // TODO Auto-generated catch block
163                         e.printStackTrace();
164                         return false;
165                 }
166
167                 final Element rootNode = xmlDocument.getDocumentElement();
168                 if (rootNode.getNodeName() != "glom_document") {
169                         Log.error("Unexpected XML root node name found: " + rootNode.getNodeName());
170                         return false;
171                 }
172
173                 databaseTitle.setTitleOriginal(rootNode.getAttribute(ATTRIBUTE_TITLE));
174
175                 // We first load the fields, relationships, etc,
176                 // for all tables:
177                 final List<Node> listTableNodes = getChildrenByTagName(rootNode, NODE_TABLE);
178                 for (final Node node : listTableNodes) {
179                         if (!(node instanceof Element))
180                                 continue;
181
182                         final Element element = (Element) node;
183                         final TableInfo info = loadTableNodeBasic(element);
184                         tablesMap.put(info.getName(), info);
185                 }
186
187                 // We then load the layouts for all tables, because they
188                 // need the fields and relationships for all tables:
189                 for (final Node node : listTableNodes) {
190                         if (!(node instanceof Element))
191                                 continue;
192
193                         final Element element = (Element) node;
194                         final String tableName = element.getAttribute(ATTRIBUTE_NAME);
195
196                         // We first load the fields, relationships, etc:
197                         final TableInfo info = getTableInfo(tableName);
198                         if (info == null) {
199                                 continue;
200                         }
201
202                         // We then load the layouts afterwards, because they
203                         // need the fields and relationships:
204                         loadTableLayouts(element, info);
205
206                         tablesMap.put(info.getName(), info);
207                 }
208
209                 final Element nodeConnection = getElementByName(rootNode, NODE_CONNECTION);
210                 if (nodeConnection != null) {
211                         connectionServer = nodeConnection.getAttribute(ATTRIBUTE_CONNECTION_SERVER);
212                         connectionDatabase = nodeConnection.getAttribute(ATTRIBUTE_CONNECTION_DATABASE);
213                         connectionPort = getAttributeAsDecimal(nodeConnection,
214                                         nodeConnection.getAttribute(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                 final NodeList listNodes = nodeGroup.getChildNodes();
511                 final int num = listNodes.getLength();
512                 for (int i = 0; i < num; i++) {
513                         final Node node = listNodes.item(i);
514                         if (!(node instanceof Element))
515                                 continue;
516
517                         final Element element = (Element) node;
518                         final String tagName = element.getTagName();
519                         if (tagName == NODE_DATA_LAYOUT_GROUP) {
520                                 final LayoutGroup childGroup = new LayoutGroup();
521                                 loadDataLayoutGroup(element, childGroup, tableName);
522                                 group.addItem(childGroup);
523                         } else if (tagName == NODE_DATA_LAYOUT_NOTEBOOK) {
524                                 final LayoutItemNotebook childGroup = new LayoutItemNotebook();
525                                 loadDataLayoutGroup(element, childGroup, tableName);
526                                 group.addItem(childGroup);
527                         } else if (tagName == NODE_DATA_LAYOUT_PORTAL) {
528                                 final LayoutItemPortal childGroup = new LayoutItemPortal();
529                                 loadDataLayoutPortal(element, childGroup, tableName);
530                                 group.addItem(childGroup);
531                         } else if (element.getTagName() == NODE_DATA_LAYOUT_ITEM) {
532                                 final LayoutItemField item = new LayoutItemField();
533                                 loadDataLayoutItemField(element, item, tableName);
534                                 group.addItem(item);
535                         } else if (element.getTagName() == NODE_DATA_LAYOUT_ITEM_GROUPBY) {
536                                 final LayoutItemGroupBy item = new LayoutItemGroupBy();
537                                 loadDataLayoutItemGroupBy(element, item, tableName);
538                                 group.addItem(item);
539                         }
540                 }
541         }
542
543         /**
544          * @param element
545          * @param item
546          * @param tableName
547          */
548         private void loadDataLayoutItemGroupBy(final Element element, final LayoutItemGroupBy item, final String tableName) {
549                 loadDataLayoutGroup(element, item, tableName);
550
551                 final Element elementGroupBy = getElementByName(element, NODE_GROUPBY);
552                 if (elementGroupBy == null) {
553                         return;
554                 }
555
556                 final LayoutItemField fieldGroupBy = new LayoutItemField();
557                 loadDataLayoutItemField(elementGroupBy, fieldGroupBy, tableName);
558                 item.setFieldGroupBy(fieldGroupBy);
559
560                 final Element elementSecondaryFields = getElementByName(element, NODE_SECONDARY_FIELDS);
561                 if (elementSecondaryFields == null) {
562                         return;
563                 }
564
565                 final Element elementLayoutGroup = getElementByName(elementSecondaryFields, NODE_DATA_LAYOUT_GROUP);
566                 if (elementLayoutGroup != null) {
567                         final LayoutGroup secondaryLayoutGroup = new LayoutGroup();
568                         loadDataLayoutGroup(elementLayoutGroup, secondaryLayoutGroup, tableName);
569                         item.setSecondaryFields(secondaryLayoutGroup);
570                 }
571         }
572
573         /**
574          * @param element
575          * @param item
576          */
577         private void loadDataLayoutItemField(final Element element, final LayoutItemField item, final String tableName) {
578                 loadTitle(element, item);
579                 loadUsesRelationship(element, tableName, item);
580
581                 // Get the actual field:
582                 final String fieldName = item.getName();
583                 final String inTableName = item.getTableUsed(tableName);
584                 final Field field = getField(inTableName, fieldName);
585                 item.setFullFieldDetails(field);
586
587                 item.setUseDefaultFormatting(getAttributeAsBoolean(element, ATTRIBUTE_USE_DEFAULT_FORMATTING));
588
589                 final Element elementFormatting = getElementByName(element, NODE_FORMATTING);
590                 if (elementFormatting != null) {
591                         loadFormatting(elementFormatting, item.getFormatting());
592                 }
593         }
594
595         /**
596          * @param element
597          * @param childGroup
598          */
599         private void loadDataLayoutPortal(final Element element, final LayoutItemPortal portal, final String tableName) {
600                 loadUsesRelationship(element, tableName, portal);
601                 final String relatedTableName = portal.getTableUsed(tableName);
602                 loadDataLayoutGroup(element, portal, relatedTableName);
603
604                 final Element elementNavigation = getElementByName(element, NODE_DATA_LAYOUT_PORTAL_NAVIGATIONRELATIONSHIP);
605                 if (elementNavigation != null) {
606                         final String navigationTypeAsString = elementNavigation.getAttribute(ATTRIBUTE_PORTAL_NAVIGATION_TYPE);
607                         if (StringUtils.isEmpty(navigationTypeAsString)
608                                         || navigationTypeAsString == ATTRIBUTE_PORTAL_NAVIGATION_TYPE_AUTOMATIC) {
609                                 portal.setNavigationType(LayoutItemPortal.NavigationType.NAVIGATION_AUTOMATIC);
610                         } else if (navigationTypeAsString == ATTRIBUTE_PORTAL_NAVIGATION_TYPE_NONE) {
611                                 portal.setNavigationType(LayoutItemPortal.NavigationType.NAVIGATION_NONE);
612                         } else if (navigationTypeAsString == ATTRIBUTE_PORTAL_NAVIGATION_TYPE_SPECIFIC) {
613                                 // Read the specified relationship name:
614                                 final UsesRelationship relationshipNavigationSpecific = new UsesRelationshipImpl();
615                                 loadUsesRelationship(elementNavigation, relatedTableName, relationshipNavigationSpecific);
616                                 portal.setNavigationRelationshipSpecific(relationshipNavigationSpecific);
617                         }
618                 }
619
620         }
621
622         /**
623          * @param element
624          * @param field
625          */
626         private void loadField(final Element element, final Field field) {
627                 loadTitle(element, field);
628
629                 Field.GlomFieldType fieldType = Field.GlomFieldType.TYPE_INVALID;
630                 final String fieldTypeStr = element.getAttribute(ATTRIBUTE_FIELD_TYPE);
631                 if (!StringUtils.isEmpty(fieldTypeStr)) {
632                         if (fieldTypeStr.equals("Boolean")) {
633                                 fieldType = Field.GlomFieldType.TYPE_BOOLEAN;
634                         } else if (fieldTypeStr.equals("Date")) {
635                                 fieldType = Field.GlomFieldType.TYPE_DATE;
636                         } else if (fieldTypeStr.equals("Image")) {
637                                 fieldType = Field.GlomFieldType.TYPE_IMAGE;
638                         } else if (fieldTypeStr.equals("Number")) {
639                                 fieldType = Field.GlomFieldType.TYPE_NUMERIC;
640                         } else if (fieldTypeStr.equals("Text")) {
641                                 fieldType = Field.GlomFieldType.TYPE_TEXT;
642                         } else if (fieldTypeStr.equals("Time")) {
643                                 fieldType = Field.GlomFieldType.TYPE_TIME;
644                         }
645                 }
646
647                 field.setGlomFieldType(fieldType);
648
649                 field.setPrimaryKey(getAttributeAsBoolean(element, ATTRIBUTE_PRIMARY_KEY));
650                 loadTitle(element, field);
651
652                 final Element elementFormatting = getElementByName(element, NODE_FORMATTING);
653                 if (elementFormatting != null) {
654                         loadFormatting(elementFormatting, field.getFormatting());
655                 }
656         }
657
658         /**
659          * @param elementFormatting
660          * @param formatting
661          */
662         private void loadFormatting(final Element elementFormatting, final Formatting formatting) {
663                 if (elementFormatting == null)
664                         return;
665
666                 if (formatting == null)
667                         return;
668
669                 // formatting.setTextFormatMultiline(getAttributeAsBoolean(elementFormatting, ATTRIBUTE_TEXT_FORMAT_MULTILINE));
670
671                 final NumericFormat numericFormatting = formatting.getNumericFormat();
672                 if (numericFormatting != null) {
673                         numericFormatting.setUseThousandsSeparator(getAttributeAsBoolean(elementFormatting,
674                                         ATTRIBUTE_USE_THOUSANDS_SEPARATOR));
675                         numericFormatting.setDecimalPlaces(getAttributeAsDecimal(elementFormatting, ATTRIBUTE_DECIMAL_PLACES));
676                 }
677
678         }
679
680         /**
681          * @param element
682          * @param reportNode
683          */
684         private void loadReport(final Element element, final Report report, final String tableName) {
685                 report.setName(element.getAttribute(ATTRIBUTE_NAME));
686                 loadTitle(element, report);
687
688                 final List<LayoutGroup> listLayoutGroups = loadLayoutNode(element, tableName);
689
690                 // A report can actually only have one LayoutGroup,
691                 // though it uses the same XML structure as List and Details layouts,
692                 // which (wrongly) suggests that it can have more than one group.
693                 LayoutGroup layoutGroup = null;
694                 if (!listLayoutGroups.isEmpty()) {
695                         layoutGroup = listLayoutGroups.get(0);
696                 }
697
698                 report.setLayoutGroup(layoutGroup);
699         }
700
701         private TableInfo getTableInfo(final String tableName) {
702                 return tablesMap.get(tableName);
703         }
704
705         public enum HostingMode {
706                 HOSTING_MODE_POSTGRES_CENTRAL, HOSTING_MODE_POSTGRES_SELF, HOSTING_MODE_SQLITE
707         };
708
709         public String getDatabaseTitle(final String locale) {
710                 return databaseTitle.getTitle(locale);
711         }
712
713         public String getDatabaseTitleOriginal() {
714                 return databaseTitle.getTitleOriginal();
715         }
716
717         public List<String> getTranslationAvailableLocales() {
718                 return translationAvailableLocales;
719         }
720
721         public Document.HostingMode getHostingMode() {
722                 return HostingMode.HOSTING_MODE_POSTGRES_CENTRAL; // TODO
723         }
724
725         public String getConnectionServer() {
726                 return connectionServer;
727         }
728
729         public long getConnectionPort() {
730                 return connectionPort;
731         }
732
733         public String getConnectionDatabase() {
734                 return connectionDatabase;
735         }
736
737         public List<String> getTableNames() {
738                 // TODO: Return a Set?
739                 return new ArrayList<String>(tablesMap.keySet());
740         }
741
742         public boolean getTableIsHidden(final String tableName) {
743                 final TableInfo info = getTableInfo(tableName);
744                 if (info == null) {
745                         return false;
746                 }
747
748                 return info.isHidden;
749         }
750
751         public String getTableTitle(final String tableName, final String locale) {
752                 final TableInfo info = getTableInfo(tableName);
753                 if (info == null) {
754                         return "";
755                 }
756
757                 return info.getTitle(locale);
758         }
759
760         public String getDefaultTable() {
761                 for (final TableInfo info : tablesMap.values()) {
762                         if (info.isDefault) {
763                                 return info.getName();
764                         }
765                 }
766
767                 return "";
768         }
769
770         public boolean getTableIsKnown(final String tableName) {
771                 final TableInfo info = getTableInfo(tableName);
772                 if (info == null) {
773                         return false;
774                 }
775
776                 return true;
777         }
778
779         public List<Field> getTableFields(final String tableName) {
780                 final TableInfo info = getTableInfo(tableName);
781                 if (info == null)
782                         return null;
783
784                 return new ArrayList<Field>(info.fieldsMap.values());
785         }
786
787         public Field getField(final String tableName, final String strFieldName) {
788                 final TableInfo info = getTableInfo(tableName);
789                 if (info == null)
790                         return null;
791
792                 return info.fieldsMap.get(strFieldName);
793         }
794
795         public List<LayoutGroup> getDataLayoutGroups(final String layoutName, final String parentTableName) {
796                 final TableInfo info = getTableInfo(parentTableName);
797                 if (info == null)
798                         return new ArrayList<LayoutGroup>();
799
800                 if (layoutName == LAYOUT_NAME_DETAILS) {
801                         return info.layoutGroupsDetails;
802                 } else if (layoutName == LAYOUT_NAME_LIST) {
803                         return info.layoutGroupsList;
804                 } else {
805                         return new ArrayList<LayoutGroup>();
806                 }
807         }
808
809         public List<String> getReportNames(final String tableName) {
810                 final TableInfo info = getTableInfo(tableName);
811                 if (info == null)
812                         return new ArrayList<String>();
813
814                 return new ArrayList<String>(info.reportsMap.keySet());
815         }
816
817         public Report getReport(final String tableName, final String reportName) {
818                 final TableInfo info = getTableInfo(tableName);
819                 if (info == null)
820                         return null;
821
822                 return info.reportsMap.get(reportName);
823         }
824
825         /**
826          * @param tableName
827          * @param field
828          * @return
829          */
830         public Relationship getFieldUsedInRelationshipToOne(final String tableName, final LayoutItemField layoutField) {
831
832                 if (layoutField == null) {
833                         Log.error("layoutField was null");
834                         return null;
835                 }
836
837                 Relationship result = null;
838
839                 final String tableUsed = layoutField.getTableUsed(tableName);
840                 final TableInfo info = getTableInfo(tableUsed);
841                 if (info == null) {
842                         // This table is special. We would not create a relationship to it using a field:
843                         // if(tableUsed == GLOM_STANDARD_TABLE_PREFS_TABLE_NAME)
844                         // return result;
845
846                         Log.error("table not found: " + tableUsed);
847                         return null;
848                 }
849
850                 // Look at each relationship:
851                 final String fieldName = layoutField.getName();
852                 for (final Relationship relationship : info.relationshipsMap.values()) {
853                         if (relationship != null) {
854                                 // If the relationship uses the field
855                                 if (relationship.getFromField() == fieldName) {
856                                         // if the to_table is not hidden:
857                                         if (!getTableIsHidden(relationship.getToTable())) {
858                                                 // TODO_Performance: The use of this convenience method means we get the full relationship
859                                                 // information again:
860                                                 if (getRelationshipIsToOne(tableName, relationship.getName())) {
861                                                         result = relationship;
862                                                 }
863                                         }
864                                 }
865                         }
866                 }
867
868                 return result;
869         }
870
871         /**
872          * @param tableName
873          * @param relationshipName
874          * @return
875          */
876         private boolean getRelationshipIsToOne(final String tableName, final String relationshipName) {
877                 final Relationship relationship = getRelationship(tableName, relationshipName);
878                 if (relationship != null) {
879                         final Field fieldTo = getField(relationship.getToTable(), relationship.getToField());
880                         if (fieldTo != null) {
881                                 return (fieldTo.getPrimaryKey() || fieldTo.getUniqueKey());
882                         }
883                 }
884
885                 return false;
886         }
887
888         /**
889          * @param tableName
890          * @param relationshipName
891          * @return
892          */
893         private Relationship getRelationship(final String tableName, final String relationshipName) {
894                 final TableInfo info = getTableInfo(tableName);
895                 if (info == null) {
896                         Log.error("table not found: " + tableName);
897                         return null;
898                 }
899
900                 return info.relationshipsMap.get(relationshipName);
901         }
902
903         public class TableToViewDetails {
904                 public String tableName;
905                 public UsesRelationship usesRelationship;
906         }
907
908         private class FieldIdentifies {
909                 public LayoutItemField field;
910                 public Relationship usedInRelationShip;
911         }
912
913         /**
914          * @param tableName
915          *            Output parameter
916          * @param relationship
917          * @param portal
918          *            TODO
919          */
920         public TableToViewDetails getPortalSuitableTableToViewDetails(LayoutItemPortal portal) {
921                 UsesRelationship navigationRelationship = null;
922
923                 // Check whether a relationship was specified:
924                 if (portal.getNavigationType() == NavigationType.NAVIGATION_AUTOMATIC) {
925                         navigationRelationship = getPortalNavigationRelationshipAutomatic(portal);
926                 } else {
927                         navigationRelationship = portal.getNavigationRelationshipSpecific();
928                 }
929
930                 // Get the navigation table name from the chosen relationship:
931                 String directlyRelatedTableName = portal.getTableUsed("" /* not relevant */);
932
933                 // The navigation_table_name (and therefore, the table_name output parameter,
934                 // as well) stays empty if the navrel type was set to none.
935                 String navigationTableName = null;
936                 if (navigationRelationship != null) {
937                         navigationTableName = navigationRelationship.getTableUsed(directlyRelatedTableName);
938                 } else if (portal.getNavigationType() != NavigationType.NAVIGATION_NONE) {
939                         // An empty result from get_portal_navigation_relationship_automatic() or
940                         // get_navigation_relationship_specific() means we should use the directly related table:
941                         navigationTableName = directlyRelatedTableName;
942                 }
943
944                 if (StringUtils.isEmpty(navigationTableName)) {
945                         return null;
946                 }
947
948                 if (this == null) {
949                         Log.error("document is null.");
950                         return null;
951                 }
952
953                 if (getTableIsHidden(navigationTableName)) {
954                         Log.error("navigation_table_name indicates a hidden table: " + navigationTableName);
955                         return null;
956                 }
957
958                 TableToViewDetails result = new TableToViewDetails();
959                 result.tableName = navigationTableName;
960                 result.usesRelationship = navigationRelationship;
961                 return result;
962         }
963
964         /**
965          * @param portal
966          *            TODO
967          * @return
968          */
969         private UsesRelationship getPortalNavigationRelationshipAutomatic(LayoutItemPortal portal) {
970                 if (this == null) {
971                         return null;
972                 }
973
974                 // If the related table is not hidden then we can just navigate to that:
975                 final String direct_related_table_name = portal.getTableUsed("" /* parent table - not relevant */);
976                 if (!getTableIsHidden(direct_related_table_name)) {
977                         // Non-hidden tables can just be shown directly. Navigate to it:
978                         return null;
979                 } else {
980                         // If the related table is hidden,
981                         // then find a suitable related non-hidden table by finding the first layout field that mentions one:
982                         final LayoutItemField field = getPortalFieldIsFromNonHiddenRelatedRecord(portal);
983                         if (field != null) {
984                                 return field; // Returns the UsesRelationship base part. (A relationship belonging to the portal's
985                                                                 // related table.)
986                         } else {
987                                 // Instead, find a key field that's used in a relationship,
988                                 // and pretend that we are showing the to field as a related field:
989                                 final FieldIdentifies fieldIndentifies = getPortalFieldIdentifiesNonHiddenRelatedRecord(portal);
990                                 if (fieldIndentifies != null) {
991                                         if (fieldIndentifies.usedInRelationShip != null) {
992                                                 UsesRelationship result = new UsesRelationshipImpl();
993                                                 result.setRelationship(fieldIndentifies.usedInRelationShip);
994                                                 return result;
995                                         }
996                                 }
997                         }
998                 }
999
1000                 // There was no suitable related table to show:
1001                 return null;
1002         }
1003
1004         /**
1005          * @param portal
1006          *            TODO
1007          * @return
1008          */
1009         private LayoutItemField getPortalFieldIsFromNonHiddenRelatedRecord(LayoutItemPortal portal) {
1010                 // Find the first field that is from a non-hidden related table.
1011
1012                 if (this == null) {
1013                         return null;
1014                 }
1015
1016                 LayoutItemField result = null;
1017
1018                 final String parent_table_name = portal.getTableUsed("" /* parent table - not relevant */);
1019
1020                 final List<LayoutItem> items = portal.getItems();
1021                 for (LayoutItem item : items) {
1022                         if (item instanceof LayoutItemField) {
1023                                 LayoutItemField field = (LayoutItemField) item;
1024                                 if (field.getHasRelationshipName()) {
1025                                         final String table_name = field.getTableUsed(parent_table_name);
1026                                         if (!(getTableIsHidden(table_name)))
1027                                                 return field;
1028                                 }
1029                         }
1030                 }
1031
1032                 return result;
1033         }
1034
1035         /**
1036          * @param used_in_relationship
1037          * @param portal
1038          *            TODO
1039          * @return
1040          */
1041         private FieldIdentifies getPortalFieldIdentifiesNonHiddenRelatedRecord(LayoutItemPortal portal) {
1042                 // Find the first field that is from a non-hidden related table.
1043
1044                 if (this == null) {
1045                         Log.error("document is null");
1046                         return null;
1047                 }
1048
1049                 final String parent_table_name = portal.getTableUsed("" /* parent table - not relevant */);
1050
1051                 List<LayoutItem> items = portal.getItems();
1052                 for (LayoutItem item : items) {
1053                         if (item instanceof LayoutItemField) {
1054                                 LayoutItemField field = (LayoutItemField) item;
1055                                 if (field.getHasRelationshipName()) {
1056                                         final Relationship relationship = getFieldUsedInRelationshipToOne(parent_table_name, field);
1057                                         if (relationship != null) {
1058                                                 final String table_name = relationship.getToTable();
1059                                                 if (!StringUtils.isEmpty(table_name)) {
1060                                                         if (!(getTableIsHidden(table_name))) {
1061
1062                                                                 FieldIdentifies result = new FieldIdentifies();
1063                                                                 result.field = field;
1064                                                                 result.usedInRelationShip = relationship;
1065                                                                 return result;
1066                                                         }
1067                                                 }
1068                                         }
1069                                 }
1070                         }
1071                 }
1072
1073                 return null;
1074         }
1075 }