2 * Copyright (C) 2012 Openismus GmbH
4 * This file is part of GWT-Glom.
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.
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
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/>.
20 package org.glom.web.server.libglom;
22 import java.io.IOException;
23 import java.util.ArrayList;
24 import java.util.Hashtable;
25 import java.util.List;
27 import javax.xml.parsers.DocumentBuilder;
28 import javax.xml.parsers.DocumentBuilderFactory;
29 import javax.xml.parsers.ParserConfigurationException;
31 import org.apache.commons.lang3.StringUtils;
32 import org.glom.web.shared.libglom.CustomTitle;
33 import org.glom.web.shared.libglom.Field;
34 import org.glom.web.shared.libglom.NumericFormat;
35 import org.glom.web.shared.libglom.Relationship;
36 import org.glom.web.shared.libglom.Report;
37 import org.glom.web.shared.libglom.Translatable;
38 import org.glom.web.shared.libglom.layout.Formatting;
39 import org.glom.web.shared.libglom.layout.LayoutGroup;
40 import org.glom.web.shared.libglom.layout.LayoutItem;
41 import org.glom.web.shared.libglom.layout.LayoutItemField;
42 import org.glom.web.shared.libglom.layout.LayoutItemNotebook;
43 import org.glom.web.shared.libglom.layout.LayoutItemPortal;
44 import org.glom.web.shared.libglom.layout.LayoutItemPortal.NavigationType;
45 import org.glom.web.shared.libglom.layout.TableToViewDetails;
46 import org.glom.web.shared.libglom.layout.UsesRelationship;
47 import org.glom.web.shared.libglom.layout.UsesRelationshipImpl;
48 import org.glom.web.shared.libglom.layout.reportparts.LayoutItemGroupBy;
49 import org.jfree.util.Log;
50 import org.w3c.dom.Element;
51 import org.w3c.dom.Node;
52 import org.w3c.dom.NodeList;
53 import org.xml.sax.SAXException;
56 * @author Murray Cumming <murrayc@openismus.com>
59 public class Document {
61 @SuppressWarnings("serial")
62 private static class TableInfo extends Translatable {
63 public boolean isDefault;
64 public boolean isHidden;
66 private final Hashtable<String, Field> fieldsMap = new Hashtable<String, Field>();
67 private final Hashtable<String, Relationship> relationshipsMap = new Hashtable<String, Relationship>();
68 private final Hashtable<String, Report> reportsMap = new Hashtable<String, Report>();
70 private List<LayoutGroup> layoutGroupsList = new ArrayList<LayoutGroup>();
71 private List<LayoutGroup> layoutGroupsDetails = new ArrayList<LayoutGroup>();
74 private String fileURI = "";
75 private org.w3c.dom.Document xmlDocument = null;
77 private Translatable databaseTitle = new Translatable();
78 private String translationOriginalLocale = "";
79 private List<String> translationAvailableLocales = new ArrayList<String>();
80 private String connectionServer = "";
81 private String connectionDatabase = "";
82 private int connectionPort = 0;
83 private final Hashtable<String, TableInfo> tablesMap = new Hashtable<String, TableInfo>();
85 private static final String ATTRIBUTE_TRANSLATION_ORIGINAL_LOCALE = "translation_original_locale";
86 private static final String NODE_CONNECTION = "connection";
87 private static final String ATTRIBUTE_CONNECTION_SERVER = "server";
88 private static final String ATTRIBUTE_CONNECTION_DATABASE = "database";
89 private static final String ATTRIBUTE_CONNECTION_PORT = "port";
90 private static final String NODE_TABLE = "table";
91 private static final String ATTRIBUTE_NAME = "name";
92 private static final String ATTRIBUTE_TITLE = "title";
93 private static final String ATTRIBUTE_DEFAULT = "default";
94 private static final String ATTRIBUTE_HIDDEN = "hidden";
95 private static final String NODE_TRANSLATIONS_SET = "trans_set";
96 private static final String NODE_TRANSLATIONS = "trans";
97 private static final String ATTRIBUTE_TRANSLATION_LOCALE = "loc";
98 private static final String ATTRIBUTE_TRANSLATION_TITLE = "val";
99 private static final String NODE_REPORTS = "reports";
100 private static final String NODE_REPORT = "report";
101 private static final String NODE_FIELDS = "fields";
102 private static final String NODE_FIELD = "field";
103 private static final String ATTRIBUTE_PRIMARY_KEY = "primary_key";
104 private static final String ATTRIBUTE_FIELD_TYPE = "type";
105 private static final String NODE_FORMATTING = "formatting";
106 // private static final String ATTRIBUTE_TEXT_FORMAT_MULTILINE = "format_text_multiline";
107 private static final String ATTRIBUTE_USE_THOUSANDS_SEPARATOR = "format_thousands_separator";
108 private static final String ATTRIBUTE_DECIMAL_PLACES = "format_decimal_places";
109 private static final String NODE_RELATIONSHIPS = "relationships";
110 private static final String NODE_RELATIONSHIP = "relationship";
111 private static final String ATTRIBUTE_RELATIONSHIP_FROM_FIELD = "key";
112 private static final String ATTRIBUTE_RELATIONSHIP_TO_TABLE = "other_table";
113 private static final String ATTRIBUTE_RELATIONSHIP_TO_FIELD = "other_key";
114 private static final String NODE_DATA_LAYOUTS = "data_layouts";
115 private static final String NODE_DATA_LAYOUT = "data_layout";
116 private static final String NODE_DATA_LAYOUT_GROUPS = "data_layout_groups";
117 private static final String NODE_DATA_LAYOUT_GROUP = "data_layout_group";
118 private static final String ATTRIBUTE_LAYOUT_GROUP_COLUMNS_COUNT = "columns_count";
119 private static final String NODE_DATA_LAYOUT_NOTEBOOK = "data_layout_notebook";
120 private static final String NODE_DATA_LAYOUT_PORTAL = "data_layout_portal";
121 private static final String NODE_DATA_LAYOUT_PORTAL_NAVIGATIONRELATIONSHIP = "portal_navigation_relationship";
122 private static final String ATTRIBUTE_PORTAL_NAVIGATION_TYPE = "navigation_type";
123 private static final String ATTRIBUTE_PORTAL_NAVIGATION_TYPE_AUTOMATIC = "automatic";
124 private static final String ATTRIBUTE_PORTAL_NAVIGATION_TYPE_SPECIFIC = "specific";
125 private static final String ATTRIBUTE_PORTAL_NAVIGATION_TYPE_NONE = "none";
126 private static final String ATTRIBUTE_RELATIONSHIP_NAME = "relationship";
127 private static final String ATTRIBUTE_RELATED_RELATIONSHIP_NAME = "related_relationship";
128 private static final String NODE_DATA_LAYOUT_ITEM = "data_layout_item";
129 private static final String NODE_CUSTOM_TITLE = "title_custom";
130 private static final String ATTRIBUTE_CUSTOM_TITLE_USE_CUSTOM = "use_custom";
131 private static final String NODE_DATA_LAYOUT_ITEM_GROUPBY = "data_layout_item_groupby";
132 private static final String NODE_GROUPBY = "groupby";
133 private static final String NODE_SECONDARY_FIELDS = "secondary_fields";
134 private static final String ATTRIBUTE_USE_DEFAULT_FORMATTING = "use_default_formatting";
135 private static final String LAYOUT_NAME_DETAILS = "details";
136 private static final String LAYOUT_NAME_LIST = "list";
138 public void setFileURI(final String fileURI) {
139 this.fileURI = fileURI;
142 public String getFileURI() {
146 // TODO: Make sure these have the correct values.
147 public enum LoadFailureCodes {
148 LOAD_FAILURE_CODE_NONE, LOAD_FAILURE_CODE_NOT_FOUND, LOAD_FAILURE_CODE_FILE_VERSION_TOO_NEW
151 public boolean load() {
152 final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
153 DocumentBuilder documentBuilder;
155 documentBuilder = dbf.newDocumentBuilder();
156 } catch (final ParserConfigurationException e) {
157 // TODO Auto-generated catch block
163 xmlDocument = documentBuilder.parse(fileURI);
164 } catch (final SAXException e) {
165 // TODO Auto-generated catch block
168 } catch (final IOException e) {
169 // TODO Auto-generated catch block
174 final Element rootNode = xmlDocument.getDocumentElement();
175 if (rootNode.getNodeName() != "glom_document") {
176 Log.error("Unexpected XML root node name found: " + rootNode.getNodeName());
180 databaseTitle.setTitleOriginal(rootNode.getAttribute(ATTRIBUTE_TITLE));
182 translationOriginalLocale = rootNode.getAttribute(ATTRIBUTE_TRANSLATION_ORIGINAL_LOCALE);
183 translationAvailableLocales.add(translationOriginalLocale); // Just a cache.
185 final Element nodeConnection = getElementByName(rootNode, NODE_CONNECTION);
186 if (nodeConnection != null) {
187 connectionServer = nodeConnection.getAttribute(ATTRIBUTE_CONNECTION_SERVER);
188 connectionDatabase = nodeConnection.getAttribute(ATTRIBUTE_CONNECTION_DATABASE);
189 connectionPort = getAttributeAsDecimal(nodeConnection, ATTRIBUTE_CONNECTION_PORT);
192 // We first load the fields, relationships, etc,
194 final List<Node> listTableNodes = getChildrenByTagName(rootNode, NODE_TABLE);
195 for (final Node node : listTableNodes) {
196 if (!(node instanceof Element))
199 final Element element = (Element) node;
200 final TableInfo info = loadTableNodeBasic(element);
201 tablesMap.put(info.getName(), info);
204 // We then load the layouts for all tables, because they
205 // need the fields and relationships for all tables:
206 for (final Node node : listTableNodes) {
207 if (!(node instanceof Element))
210 final Element element = (Element) node;
211 final String tableName = element.getAttribute(ATTRIBUTE_NAME);
213 // We first load the fields, relationships, etc:
214 final TableInfo info = getTableInfo(tableName);
219 // We then load the layouts afterwards, because they
220 // need the fields and relationships:
221 loadTableLayouts(element, info);
223 tablesMap.put(info.getName(), info);
229 private Element getElementByName(final Element parentElement, final String tagName) {
230 final List<Node> listNodes = getChildrenByTagName(parentElement, tagName);
231 if (listNodes == null)
234 if (listNodes.size() == 0)
237 return (Element) listNodes.get(0);
240 private boolean getAttributeAsBoolean(final Element node, final String attributeName) {
241 final String str = node.getAttribute(attributeName);
245 return str.equals("true");
249 * @param elementFormatting
250 * @param aTTRIBUTE_DECIMAL_PLACES2
253 private int getAttributeAsDecimal(final Element node, final String attributeName) {
254 final String str = node.getAttribute(attributeName);
255 if (StringUtils.isEmpty(str))
258 return Integer.valueOf(str);
262 * Load a title and its translations.
265 * The XML Element that may contain a title attribute and a trans_set of translations of the title.
268 private void loadTitle(final Element node, final Translatable title) {
269 title.setName(node.getAttribute(ATTRIBUTE_NAME));
271 title.setTitleOriginal(node.getAttribute(ATTRIBUTE_TITLE));
273 final Element nodeSet = getElementByName(node, NODE_TRANSLATIONS_SET);
274 if (nodeSet == null) {
278 final List<Node> listNodes = getChildrenByTagName(nodeSet, NODE_TRANSLATIONS);
279 if (listNodes == null)
282 for (final Node transNode : listNodes) {
283 if (!(transNode instanceof Element)) {
287 final Element element = (Element) transNode;
289 final String locale = element.getAttribute(ATTRIBUTE_TRANSLATION_LOCALE);
290 final String translatedTitle = element.getAttribute(ATTRIBUTE_TRANSLATION_TITLE);
291 if (!StringUtils.isEmpty(locale) && !StringUtils.isEmpty(translatedTitle)) {
292 title.setTitle(translatedTitle, locale);
294 // Remember any new translation locales in our cached list:
295 if (!translationAvailableLocales.contains(locale)) {
296 translationAvailableLocales.add(locale);
306 private TableInfo loadTableNodeBasic(final Element tableNode) {
307 final TableInfo info = new TableInfo();
308 loadTitle(tableNode, info);
309 final String tableName = info.getName();
311 info.isDefault = getAttributeAsBoolean(tableNode, ATTRIBUTE_DEFAULT);
312 info.isHidden = getAttributeAsBoolean(tableNode, ATTRIBUTE_HIDDEN);
314 // These should be loaded before the fields, because the fields use them.
315 final Element relationshipsNode = getElementByName(tableNode, NODE_RELATIONSHIPS);
316 if (relationshipsNode != null) {
317 final List<Node> listNodes = getChildrenByTagName(relationshipsNode, NODE_RELATIONSHIP);
318 for (final Node node : listNodes) {
319 if (!(node instanceof Element)) {
323 final Element element = (Element) node;
324 final Relationship relationship = new Relationship();
325 loadTitle(element, relationship);
326 relationship.setFromTable(tableName);
327 relationship.setFromField(element.getAttribute(ATTRIBUTE_RELATIONSHIP_FROM_FIELD));
328 relationship.setToTable(element.getAttribute(ATTRIBUTE_RELATIONSHIP_TO_TABLE));
329 relationship.setToField(element.getAttribute(ATTRIBUTE_RELATIONSHIP_TO_FIELD));
331 info.relationshipsMap.put(relationship.getName(), relationship);
335 final Element fieldsNode = getElementByName(tableNode, NODE_FIELDS);
336 if (fieldsNode != null) {
337 final List<Node> listNodes = getChildrenByTagName(fieldsNode, NODE_FIELD);
338 for (final Node node : listNodes) {
339 if (!(node instanceof Element)) {
343 final Element element = (Element) node;
344 final Field field = new Field();
345 loadField(element, field);
347 info.fieldsMap.put(field.getName(), field);
358 private void loadTableLayouts(final Element tableNode, final TableInfo info) {
359 final String tableName = info.getName();
361 final Element layoutsNode = getElementByName(tableNode, NODE_DATA_LAYOUTS);
362 if (layoutsNode != null) {
363 final List<Node> listNodes = getChildrenByTagName(layoutsNode, NODE_DATA_LAYOUT);
364 for (final Node node : listNodes) {
365 if (!(node instanceof Element)) {
369 final Element element = (Element) node;
370 final String name = element.getAttribute("name");
371 final List<LayoutGroup> listLayoutGroups = loadLayoutNode(element, tableName);
372 if (name.equals(LAYOUT_NAME_DETAILS)) {
373 info.layoutGroupsDetails = listLayoutGroups;
374 } else if (name.equals(LAYOUT_NAME_LIST)) {
375 info.layoutGroupsList = listLayoutGroups;
377 Log.error("loadTableNode(): unexpected layout name: " + name);
382 final Element reportsNode = getElementByName(tableNode, NODE_REPORTS);
383 if (reportsNode != null) {
384 final List<Node> listNodes = getChildrenByTagName(reportsNode, NODE_REPORT);
385 for (final Node node : listNodes) {
386 if (!(node instanceof Element)) {
390 final Element element = (Element) node;
391 final Report report = new Report();
392 loadReport(element, report, tableName);
394 info.reportsMap.put(report.getName(), report);
403 private List<LayoutGroup> loadLayoutNode(final Element node, final String tableName) {
408 final List<LayoutGroup> result = new ArrayList<LayoutGroup>();
410 final List<Node> listNodes = getChildrenByTagName(node, NODE_DATA_LAYOUT_GROUPS);
411 for (final Node nodeGroups : listNodes) {
412 if (!(nodeGroups instanceof Element)) {
416 final Element elementGroups = (Element) nodeGroups;
418 final NodeList list = elementGroups.getChildNodes();
419 final int num = list.getLength();
420 for (int i = 0; i < num; i++) {
421 final Node nodeLayoutGroup = list.item(i);
422 if (nodeLayoutGroup == null) {
426 if (!(nodeLayoutGroup instanceof Element)) {
430 final Element element = (Element) nodeLayoutGroup;
431 final String tagName = element.getTagName();
432 if (tagName == NODE_DATA_LAYOUT_GROUP) {
433 final LayoutGroup group = new LayoutGroup();
434 loadDataLayoutGroup(element, group, tableName);
436 } else if (tagName == NODE_DATA_LAYOUT_NOTEBOOK) {
437 final LayoutItemNotebook group = new LayoutItemNotebook();
438 loadDataLayoutGroup(element, group, tableName);
440 } else if (tagName == NODE_DATA_LAYOUT_PORTAL) {
441 final LayoutItemPortal portal = new LayoutItemPortal();
442 loadDataLayoutPortal(element, portal, tableName);
456 private void loadUsesRelationship(final Element element, final String tableName, final UsesRelationship item) {
457 if (element == null) {
465 final String relationshipName = element.getAttribute(ATTRIBUTE_RELATIONSHIP_NAME);
466 Relationship relationship = null;
467 if (!StringUtils.isEmpty(relationshipName)) {
468 // std::cout << " debug in : tableName=" << tableName << ", relationshipName=" << relationship_name <<
470 relationship = getRelationship(tableName, relationshipName);
471 item.setRelationship(relationship);
473 if (relationship == null) {
474 Log.error("relationship not found: " + relationshipName + ", in table: " + tableName);
478 // TODO: Unit test loading of doubly-related fields.
479 final String relatedRelationshipName = element.getAttribute(ATTRIBUTE_RELATED_RELATIONSHIP_NAME);
480 if (!StringUtils.isEmpty(relatedRelationshipName) && (relationship != null)) {
481 final Relationship relatedRelationship = getRelationship(relationship.getToTable(), relatedRelationshipName);
482 item.setRelatedRelationship(relatedRelationship);
484 if (relatedRelationship == null) {
485 Log.error("related relationship not found in table=" + relationship.getToTable() + ", name="
486 + relatedRelationshipName);
492 * getElementsByTagName() is recursive, but we do not want that.
498 private List<Node> getChildrenByTagName(final Element parentNode, final String tagName) {
499 final List<Node> result = new ArrayList<Node>();
501 final NodeList list = parentNode.getElementsByTagName(tagName);
502 final int num = list.getLength();
503 for (int i = 0; i < num; i++) {
504 final Node node = list.item(i);
509 final Node itemParentNode = node.getParentNode();
510 if (itemParentNode.equals(parentNode)) {
522 private void loadDataLayoutGroup(final Element nodeGroup, final LayoutGroup group, final String tableName) {
523 loadTitle(nodeGroup, group);
525 // Read the column count:
526 int columnCount = getAttributeAsDecimal(nodeGroup, ATTRIBUTE_LAYOUT_GROUP_COLUMNS_COUNT);
527 if (columnCount < 1) {
528 columnCount = 1; // 0 is a useless default.
530 group.setColumnCount(columnCount);
532 // Get the child items:
533 final NodeList listNodes = nodeGroup.getChildNodes();
534 final int num = listNodes.getLength();
535 for (int i = 0; i < num; i++) {
536 final Node node = listNodes.item(i);
537 if (!(node instanceof Element))
540 final Element element = (Element) node;
541 final String tagName = element.getTagName();
542 if (tagName == NODE_DATA_LAYOUT_GROUP) {
543 final LayoutGroup childGroup = new LayoutGroup();
544 loadDataLayoutGroup(element, childGroup, tableName);
545 group.addItem(childGroup);
546 } else if (tagName == NODE_DATA_LAYOUT_NOTEBOOK) {
547 final LayoutItemNotebook childGroup = new LayoutItemNotebook();
548 loadDataLayoutGroup(element, childGroup, tableName);
549 group.addItem(childGroup);
550 } else if (tagName == NODE_DATA_LAYOUT_PORTAL) {
551 final LayoutItemPortal childGroup = new LayoutItemPortal();
552 loadDataLayoutPortal(element, childGroup, tableName);
553 group.addItem(childGroup);
554 } else if (element.getTagName() == NODE_DATA_LAYOUT_ITEM) {
555 final LayoutItemField item = new LayoutItemField();
556 loadDataLayoutItemField(element, item, tableName);
558 } else if (element.getTagName() == NODE_DATA_LAYOUT_ITEM_GROUPBY) {
559 final LayoutItemGroupBy item = new LayoutItemGroupBy();
560 loadDataLayoutItemGroupBy(element, item, tableName);
571 private void loadDataLayoutItemGroupBy(final Element element, final LayoutItemGroupBy item, final String tableName) {
572 loadDataLayoutGroup(element, item, tableName);
574 final Element elementGroupBy = getElementByName(element, NODE_GROUPBY);
575 if (elementGroupBy == null) {
579 final LayoutItemField fieldGroupBy = new LayoutItemField();
580 loadDataLayoutItemField(elementGroupBy, fieldGroupBy, tableName);
581 item.setFieldGroupBy(fieldGroupBy);
583 final Element elementSecondaryFields = getElementByName(element, NODE_SECONDARY_FIELDS);
584 if (elementSecondaryFields == null) {
588 final Element elementLayoutGroup = getElementByName(elementSecondaryFields, NODE_DATA_LAYOUT_GROUP);
589 if (elementLayoutGroup != null) {
590 final LayoutGroup secondaryLayoutGroup = new LayoutGroup();
591 loadDataLayoutGroup(elementLayoutGroup, secondaryLayoutGroup, tableName);
592 item.setSecondaryFields(secondaryLayoutGroup);
600 private void loadDataLayoutItemField(final Element element, final LayoutItemField item, final String tableName) {
601 item.setName(element.getAttribute(ATTRIBUTE_NAME));
602 loadUsesRelationship(element, tableName, item);
604 final Element elementCustomTitle = getElementByName(element, NODE_CUSTOM_TITLE);
605 if (elementCustomTitle != null) {
606 final CustomTitle customTitle = item.getCustomTitle();
607 customTitle.setUseCustomTitle(getAttributeAsBoolean(elementCustomTitle, ATTRIBUTE_CUSTOM_TITLE_USE_CUSTOM));
608 loadTitle(elementCustomTitle, customTitle); // LayoutItemField doesn't use its own title member.
611 // Get the actual field:
612 final String fieldName = item.getName();
613 final String inTableName = item.getTableUsed(tableName);
614 final Field field = getField(inTableName, fieldName);
615 item.setFullFieldDetails(field);
617 item.setUseDefaultFormatting(getAttributeAsBoolean(element, ATTRIBUTE_USE_DEFAULT_FORMATTING));
619 final Element elementFormatting = getElementByName(element, NODE_FORMATTING);
620 if (elementFormatting != null) {
621 loadFormatting(elementFormatting, item.getFormatting());
629 private void loadDataLayoutPortal(final Element element, final LayoutItemPortal portal, final String tableName) {
630 loadUsesRelationship(element, tableName, portal);
631 final String relatedTableName = portal.getTableUsed(tableName);
632 loadDataLayoutGroup(element, portal, relatedTableName);
634 final Element elementNavigation = getElementByName(element, NODE_DATA_LAYOUT_PORTAL_NAVIGATIONRELATIONSHIP);
635 if (elementNavigation != null) {
636 final String navigationTypeAsString = elementNavigation.getAttribute(ATTRIBUTE_PORTAL_NAVIGATION_TYPE);
637 if (StringUtils.isEmpty(navigationTypeAsString)
638 || navigationTypeAsString == ATTRIBUTE_PORTAL_NAVIGATION_TYPE_AUTOMATIC) {
639 portal.setNavigationType(LayoutItemPortal.NavigationType.NAVIGATION_AUTOMATIC);
640 } else if (navigationTypeAsString == ATTRIBUTE_PORTAL_NAVIGATION_TYPE_NONE) {
641 portal.setNavigationType(LayoutItemPortal.NavigationType.NAVIGATION_NONE);
642 } else if (navigationTypeAsString == ATTRIBUTE_PORTAL_NAVIGATION_TYPE_SPECIFIC) {
643 // Read the specified relationship name:
644 final UsesRelationship relationshipNavigationSpecific = new UsesRelationshipImpl();
645 loadUsesRelationship(elementNavigation, relatedTableName, relationshipNavigationSpecific);
646 portal.setNavigationRelationshipSpecific(relationshipNavigationSpecific);
656 private void loadField(final Element element, final Field field) {
657 loadTitle(element, field);
659 Field.GlomFieldType fieldType = Field.GlomFieldType.TYPE_INVALID;
660 final String fieldTypeStr = element.getAttribute(ATTRIBUTE_FIELD_TYPE);
661 if (!StringUtils.isEmpty(fieldTypeStr)) {
662 if (fieldTypeStr.equals("Boolean")) {
663 fieldType = Field.GlomFieldType.TYPE_BOOLEAN;
664 } else if (fieldTypeStr.equals("Date")) {
665 fieldType = Field.GlomFieldType.TYPE_DATE;
666 } else if (fieldTypeStr.equals("Image")) {
667 fieldType = Field.GlomFieldType.TYPE_IMAGE;
668 } else if (fieldTypeStr.equals("Number")) {
669 fieldType = Field.GlomFieldType.TYPE_NUMERIC;
670 } else if (fieldTypeStr.equals("Text")) {
671 fieldType = Field.GlomFieldType.TYPE_TEXT;
672 } else if (fieldTypeStr.equals("Time")) {
673 fieldType = Field.GlomFieldType.TYPE_TIME;
677 field.setGlomFieldType(fieldType);
679 field.setPrimaryKey(getAttributeAsBoolean(element, ATTRIBUTE_PRIMARY_KEY));
680 loadTitle(element, field);
682 final Element elementFormatting = getElementByName(element, NODE_FORMATTING);
683 if (elementFormatting != null) {
684 loadFormatting(elementFormatting, field.getFormatting());
689 * @param elementFormatting
692 private void loadFormatting(final Element elementFormatting, final Formatting formatting) {
693 if (elementFormatting == null) {
697 if (formatting == null) {
701 // formatting.setTextFormatMultiline(getAttributeAsBoolean(elementFormatting, ATTRIBUTE_TEXT_FORMAT_MULTILINE));
703 final NumericFormat numericFormatting = formatting.getNumericFormat();
704 if (numericFormatting != null) {
705 numericFormatting.setUseThousandsSeparator(getAttributeAsBoolean(elementFormatting,
706 ATTRIBUTE_USE_THOUSANDS_SEPARATOR));
707 numericFormatting.setDecimalPlaces(getAttributeAsDecimal(elementFormatting, ATTRIBUTE_DECIMAL_PLACES));
716 private void loadReport(final Element element, final Report report, final String tableName) {
717 report.setName(element.getAttribute(ATTRIBUTE_NAME));
718 loadTitle(element, report);
720 final List<LayoutGroup> listLayoutGroups = loadLayoutNode(element, tableName);
722 // A report can actually only have one LayoutGroup,
723 // though it uses the same XML structure as List and Details layouts,
724 // which (wrongly) suggests that it can have more than one group.
725 LayoutGroup layoutGroup = null;
726 if (!listLayoutGroups.isEmpty()) {
727 layoutGroup = listLayoutGroups.get(0);
730 report.setLayoutGroup(layoutGroup);
733 private TableInfo getTableInfo(final String tableName) {
734 return tablesMap.get(tableName);
737 public enum HostingMode {
738 HOSTING_MODE_POSTGRES_CENTRAL, HOSTING_MODE_POSTGRES_SELF, HOSTING_MODE_SQLITE
741 public String getDatabaseTitle(final String locale) {
742 return databaseTitle.getTitle(locale);
745 public String getDatabaseTitleOriginal() {
746 return databaseTitle.getTitleOriginal();
749 public List<String> getTranslationAvailableLocales() {
750 return translationAvailableLocales;
753 public Document.HostingMode getHostingMode() {
754 return HostingMode.HOSTING_MODE_POSTGRES_CENTRAL; // TODO
757 public String getConnectionServer() {
758 return connectionServer;
761 public long getConnectionPort() {
762 return connectionPort;
765 public String getConnectionDatabase() {
766 return connectionDatabase;
769 public List<String> getTableNames() {
770 // TODO: Return a Set?
771 return new ArrayList<String>(tablesMap.keySet());
774 public boolean getTableIsHidden(final String tableName) {
775 final TableInfo info = getTableInfo(tableName);
780 return info.isHidden;
783 public String getTableTitle(final String tableName, final String locale) {
784 final TableInfo info = getTableInfo(tableName);
789 return info.getTitle(locale);
792 public String getDefaultTable() {
793 for (final TableInfo info : tablesMap.values()) {
794 if (info.isDefault) {
795 return info.getName();
802 public boolean getTableIsKnown(final String tableName) {
803 final TableInfo info = getTableInfo(tableName);
811 public List<Field> getTableFields(final String tableName) {
812 final TableInfo info = getTableInfo(tableName);
816 return new ArrayList<Field>(info.fieldsMap.values());
819 public Field getField(final String tableName, final String strFieldName) {
820 final TableInfo info = getTableInfo(tableName);
824 return info.fieldsMap.get(strFieldName);
827 public List<LayoutGroup> getDataLayoutGroups(final String layoutName, final String parentTableName) {
828 final TableInfo info = getTableInfo(parentTableName);
830 return new ArrayList<LayoutGroup>();
832 if (layoutName == LAYOUT_NAME_DETAILS) {
833 return info.layoutGroupsDetails;
834 } else if (layoutName == LAYOUT_NAME_LIST) {
835 return info.layoutGroupsList;
837 return new ArrayList<LayoutGroup>();
841 public List<String> getReportNames(final String tableName) {
842 final TableInfo info = getTableInfo(tableName);
844 return new ArrayList<String>();
846 return new ArrayList<String>(info.reportsMap.keySet());
849 public Report getReport(final String tableName, final String reportName) {
850 final TableInfo info = getTableInfo(tableName);
854 return info.reportsMap.get(reportName);
862 public Relationship getFieldUsedInRelationshipToOne(final String tableName, final LayoutItemField layoutField) {
864 if (layoutField == null) {
865 Log.error("layoutField was null");
869 Relationship result = null;
871 final String tableUsed = layoutField.getTableUsed(tableName);
872 final TableInfo info = getTableInfo(tableUsed);
874 // This table is special. We would not create a relationship to it using a field:
875 // if(tableUsed == GLOM_STANDARD_TABLE_PREFS_TABLE_NAME)
878 Log.error("table not found: " + tableUsed);
882 // Look at each relationship:
883 final String fieldName = layoutField.getName();
884 for (final Relationship relationship : info.relationshipsMap.values()) {
885 if (relationship != null) {
886 // If the relationship uses the field
887 if (StringUtils.equals(relationship.getFromField(), fieldName)) {
888 // if the to_table is not hidden:
889 if (!getTableIsHidden(relationship.getToTable())) {
890 // TODO_Performance: The use of this convenience method means we get the full relationship
891 // information again:
892 if (getRelationshipIsToOne(tableName, relationship.getName())) {
893 result = relationship;
905 * @param relationshipName
908 private boolean getRelationshipIsToOne(final String tableName, final String relationshipName) {
909 final Relationship relationship = getRelationship(tableName, relationshipName);
910 if (relationship != null) {
911 final Field fieldTo = getField(relationship.getToTable(), relationship.getToField());
912 if (fieldTo != null) {
913 return (fieldTo.getPrimaryKey() || fieldTo.getUniqueKey());
922 * @param relationshipName
925 private Relationship getRelationship(final String tableName, final String relationshipName) {
926 final TableInfo info = getTableInfo(tableName);
928 Log.error("table not found: " + tableName);
932 return info.relationshipsMap.get(relationshipName);
938 * @param relationship
942 public TableToViewDetails getPortalSuitableTableToViewDetails(LayoutItemPortal portal) {
943 UsesRelationship navigationRelationship = null;
945 // Check whether a relationship was specified:
946 if (portal.getNavigationType() == NavigationType.NAVIGATION_AUTOMATIC) {
947 navigationRelationship = getPortalNavigationRelationshipAutomatic(portal);
949 navigationRelationship = portal.getNavigationRelationshipSpecific();
952 // Get the navigation table name from the chosen relationship:
953 String directlyRelatedTableName = portal.getTableUsed("" /* not relevant */);
955 // The navigation_table_name (and therefore, the table_name output parameter,
956 // as well) stays empty if the navrel type was set to none.
957 String navigationTableName = null;
958 if (navigationRelationship != null) {
959 navigationTableName = navigationRelationship.getTableUsed(directlyRelatedTableName);
960 } else if (portal.getNavigationType() != NavigationType.NAVIGATION_NONE) {
961 // An empty result from get_portal_navigation_relationship_automatic() or
962 // get_navigation_relationship_specific() means we should use the directly related table:
963 navigationTableName = directlyRelatedTableName;
966 if (StringUtils.isEmpty(navigationTableName)) {
971 Log.error("document is null.");
975 if (getTableIsHidden(navigationTableName)) {
976 Log.error("navigation_table_name indicates a hidden table: " + navigationTableName);
980 TableToViewDetails result = new TableToViewDetails();
981 result.tableName = navigationTableName;
982 result.usesRelationship = navigationRelationship;
991 private UsesRelationship getPortalNavigationRelationshipAutomatic(LayoutItemPortal portal) {
996 // If the related table is not hidden then we can just navigate to that:
997 final String direct_related_table_name = portal.getTableUsed("" /* parent table - not relevant */);
998 if (!getTableIsHidden(direct_related_table_name)) {
999 // Non-hidden tables can just be shown directly. Navigate to it:
1002 // If the related table is hidden,
1003 // then find a suitable related non-hidden table by finding the first layout field that mentions one:
1004 final LayoutItemField field = getPortalFieldIsFromNonHiddenRelatedRecord(portal);
1005 if (field != null) {
1006 return field; // Returns the UsesRelationship base part. (A relationship belonging to the portal's
1009 // Instead, find a key field that's used in a relationship,
1010 // and pretend that we are showing the to field as a related field:
1011 final Relationship fieldIndentifies = getPortalFieldIdentifiesNonHiddenRelatedRecord(portal);
1012 if (fieldIndentifies != null) {
1013 UsesRelationship result = new UsesRelationshipImpl();
1014 result.setRelationship(fieldIndentifies);
1020 // There was no suitable related table to show:
1029 private LayoutItemField getPortalFieldIsFromNonHiddenRelatedRecord(LayoutItemPortal portal) {
1030 // Find the first field that is from a non-hidden related table.
1036 LayoutItemField result = null;
1038 final String parent_table_name = portal.getTableUsed("" /* parent table - not relevant */);
1040 final List<LayoutItem> items = portal.getItems();
1041 for (LayoutItem item : items) {
1042 if (item instanceof LayoutItemField) {
1043 LayoutItemField field = (LayoutItemField) item;
1044 if (field.getHasRelationshipName()) {
1045 final String table_name = field.getTableUsed(parent_table_name);
1046 if (!(getTableIsHidden(table_name)))
1056 * @param used_in_relationship
1061 private Relationship getPortalFieldIdentifiesNonHiddenRelatedRecord(LayoutItemPortal portal) {
1062 // Find the first field that is from a non-hidden related table.
1065 Log.error("document is null");
1069 final String parent_table_name = portal.getTableUsed("" /* parent table - not relevant */);
1071 List<LayoutItem> items = portal.getItems();
1072 for (LayoutItem item : items) {
1073 if (item instanceof LayoutItemField) {
1074 LayoutItemField field = (LayoutItemField) item;
1075 if (field.getHasRelationshipName()) {
1076 final Relationship relationship = getFieldUsedInRelationshipToOne(parent_table_name, field);
1077 if (relationship != null) {
1078 final String table_name = relationship.getToTable();
1079 if (!StringUtils.isEmpty(table_name)) {
1080 if (!(getTableIsHidden(table_name))) {
1081 return relationship;
1095 * @return The destination table name for navigation.
1097 public String getLayoutItemFieldShouldHaveNavigation(final String tableName, final LayoutItemField layoutItem) {
1098 if (StringUtils.isEmpty(tableName)) {
1102 if (layoutItem == null) {
1106 // Check whether the field controls a relationship,
1107 // meaning it identifies a record in another table.
1108 final Relationship fieldUsedInRelationshipToOne = getFieldUsedInRelationshipToOne(tableName, layoutItem);
1109 if (fieldUsedInRelationshipToOne != null) {
1110 return fieldUsedInRelationshipToOne.getToTable();
1113 // Check whether the field identifies a record in another table
1114 // just because it is a primary key in that table:
1115 final Field fieldInfo = layoutItem.getFullFieldDetails();
1116 final boolean fieldIsRelatedPrimaryKey = layoutItem.getHasRelationshipName() && (fieldInfo != null)
1117 && fieldInfo.getPrimaryKey();
1118 if (fieldIsRelatedPrimaryKey) {
1119 return layoutItem.getRelationship().getToTable();