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