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;
23 import java.io.IOException;
24 import java.text.DateFormat;
25 import java.text.NumberFormat;
26 import java.text.ParseException;
27 import java.util.ArrayList;
28 import java.util.Date;
29 import java.util.Hashtable;
30 import java.util.List;
31 import java.util.Locale;
32 import java.util.Map.Entry;
34 import javax.xml.parsers.DocumentBuilder;
35 import javax.xml.parsers.DocumentBuilderFactory;
36 import javax.xml.parsers.ParserConfigurationException;
37 import javax.xml.transform.OutputKeys;
38 import javax.xml.transform.Transformer;
39 import javax.xml.transform.TransformerConfigurationException;
40 import javax.xml.transform.TransformerException;
41 import javax.xml.transform.TransformerFactory;
42 import javax.xml.transform.dom.DOMSource;
43 import javax.xml.transform.stream.StreamResult;
45 import org.apache.commons.lang3.StringUtils;
46 import org.glom.web.shared.DataItem;
47 import org.glom.web.shared.libglom.CustomTitle;
48 import org.glom.web.shared.libglom.Field;
49 import org.glom.web.shared.libglom.Field.GlomFieldType;
50 import org.glom.web.shared.libglom.NumericFormat;
51 import org.glom.web.shared.libglom.Relationship;
52 import org.glom.web.shared.libglom.Report;
53 import org.glom.web.shared.libglom.Translatable;
54 import org.glom.web.shared.libglom.layout.Formatting;
55 import org.glom.web.shared.libglom.layout.LayoutGroup;
56 import org.glom.web.shared.libglom.layout.LayoutItem;
57 import org.glom.web.shared.libglom.layout.LayoutItemField;
58 import org.glom.web.shared.libglom.layout.LayoutItemNotebook;
59 import org.glom.web.shared.libglom.layout.LayoutItemPortal;
60 import org.glom.web.shared.libglom.layout.LayoutItemPortal.NavigationType;
61 import org.glom.web.shared.libglom.layout.TableToViewDetails;
62 import org.glom.web.shared.libglom.layout.UsesRelationship;
63 import org.glom.web.shared.libglom.layout.UsesRelationshipImpl;
64 import org.glom.web.shared.libglom.layout.reportparts.LayoutItemGroupBy;
65 import org.jfree.util.Log;
66 import org.w3c.dom.Element;
67 import org.w3c.dom.Node;
68 import org.w3c.dom.NodeList;
69 import org.xml.sax.SAXException;
71 import com.google.common.io.Files;
74 * @author Murray Cumming <murrayc@openismus.com>
77 public class Document {
79 @SuppressWarnings("serial")
80 private static class TableInfo extends Translatable {
81 private boolean isDefault;
82 private boolean isHidden;
84 private final Hashtable<String, Field> fieldsMap = new Hashtable<String, Field>();
85 private final Hashtable<String, Relationship> relationshipsMap = new Hashtable<String, Relationship>();
86 private final Hashtable<String, Report> reportsMap = new Hashtable<String, Report>();
88 private List<LayoutGroup> layoutGroupsList = new ArrayList<LayoutGroup>();
89 private List<LayoutGroup> layoutGroupsDetails = new ArrayList<LayoutGroup>();
91 private List<List<DataItem>> exampleRows = null;
94 private String fileURI = "";
95 private org.w3c.dom.Document xmlDocument = null;
97 private final Translatable databaseTitle = new Translatable();
98 private String translationOriginalLocale = "";
99 private final List<String> translationAvailableLocales = new ArrayList<String>();
100 private boolean isExample = false;
101 private HostingMode hostingMode = HostingMode.HOSTING_MODE_POSTGRES_CENTRAL;
102 private String connectionServer = "";
103 private String connectionDatabase = "";
104 private int connectionPort = 0;
105 private final Hashtable<String, TableInfo> tablesMap = new Hashtable<String, TableInfo>();
107 private static final String NODE_ROOT = "glom_document";
108 private static final String ATTRIBUTE_IS_EXAMPLE = "is_example";
109 private static final String ATTRIBUTE_TRANSLATION_ORIGINAL_LOCALE = "translation_original_locale";
110 private static final String NODE_CONNECTION = "connection";
111 private static final String ATTRIBUTE_CONNECTION_HOSTING_MODE = "hosting_mode";
112 private static final String ATTRIBUTE_CONNECTION_SERVER = "server";
113 private static final String ATTRIBUTE_CONNECTION_DATABASE = "database";
114 private static final String ATTRIBUTE_CONNECTION_PORT = "port";
115 private static final String NODE_TABLE = "table";
116 private static final String ATTRIBUTE_NAME = "name";
117 private static final String ATTRIBUTE_TITLE = "title";
118 private static final String ATTRIBUTE_DEFAULT = "default";
119 private static final String ATTRIBUTE_HIDDEN = "hidden";
120 private static final String NODE_TRANSLATIONS_SET = "trans_set";
121 private static final String NODE_TRANSLATIONS = "trans";
122 private static final String ATTRIBUTE_TRANSLATION_LOCALE = "loc";
123 private static final String ATTRIBUTE_TRANSLATION_TITLE = "val";
124 private static final String NODE_REPORTS = "reports";
125 private static final String NODE_REPORT = "report";
126 private static final String NODE_FIELDS = "fields";
127 private static final String NODE_FIELD = "field";
128 private static final String NODE_EXAMPLE_ROWS = "example_rows";
129 private static final String NODE_EXAMPLE_ROW = "example_row";
130 private static final String NODE_VALUE = "value";
131 private static final String ATTRIBUTE_COLUMN = "column";
132 private static final String ATTRIBUTE_PRIMARY_KEY = "primary_key";
133 private static final String ATTRIBUTE_FIELD_TYPE = "type";
134 private static final String NODE_FORMATTING = "formatting";
135 // private static final String ATTRIBUTE_TEXT_FORMAT_MULTILINE = "format_text_multiline";
136 private static final String ATTRIBUTE_USE_THOUSANDS_SEPARATOR = "format_thousands_separator";
137 private static final String ATTRIBUTE_DECIMAL_PLACES = "format_decimal_places";
138 private static final String NODE_RELATIONSHIPS = "relationships";
139 private static final String NODE_RELATIONSHIP = "relationship";
140 private static final String ATTRIBUTE_RELATIONSHIP_FROM_FIELD = "key";
141 private static final String ATTRIBUTE_RELATIONSHIP_TO_TABLE = "other_table";
142 private static final String ATTRIBUTE_RELATIONSHIP_TO_FIELD = "other_key";
143 private static final String NODE_DATA_LAYOUTS = "data_layouts";
144 private static final String NODE_DATA_LAYOUT = "data_layout";
145 private static final String NODE_DATA_LAYOUT_GROUPS = "data_layout_groups";
146 private static final String NODE_DATA_LAYOUT_GROUP = "data_layout_group";
147 private static final String ATTRIBUTE_LAYOUT_GROUP_COLUMNS_COUNT = "columns_count";
148 private static final String NODE_DATA_LAYOUT_NOTEBOOK = "data_layout_notebook";
149 private static final String NODE_DATA_LAYOUT_PORTAL = "data_layout_portal";
150 private static final String NODE_DATA_LAYOUT_PORTAL_NAVIGATIONRELATIONSHIP = "portal_navigation_relationship";
151 private static final String ATTRIBUTE_PORTAL_NAVIGATION_TYPE = "navigation_type";
152 private static final String ATTRIBUTE_PORTAL_NAVIGATION_TYPE_AUTOMATIC = "automatic";
153 private static final String ATTRIBUTE_PORTAL_NAVIGATION_TYPE_SPECIFIC = "specific";
154 private static final String ATTRIBUTE_PORTAL_NAVIGATION_TYPE_NONE = "none";
155 private static final String ATTRIBUTE_RELATIONSHIP_NAME = "relationship";
156 private static final String ATTRIBUTE_RELATED_RELATIONSHIP_NAME = "related_relationship";
157 private static final String NODE_DATA_LAYOUT_ITEM = "data_layout_item";
158 private static final String NODE_CUSTOM_TITLE = "title_custom";
159 private static final String ATTRIBUTE_CUSTOM_TITLE_USE_CUSTOM = "use_custom";
160 private static final String NODE_DATA_LAYOUT_ITEM_GROUPBY = "data_layout_item_groupby";
161 private static final String NODE_GROUPBY = "groupby";
162 private static final String NODE_SECONDARY_FIELDS = "secondary_fields";
163 private static final String ATTRIBUTE_USE_DEFAULT_FORMATTING = "use_default_formatting";
164 private static final String LAYOUT_NAME_DETAILS = "details";
165 private static final String LAYOUT_NAME_LIST = "list";
166 private static final String QUOTE_FOR_FILE_FORMAT = "\"";
168 public void setFileURI(final String fileURI) {
169 this.fileURI = fileURI;
172 public String getFileURI() {
176 // TODO: Make sure these have the correct values.
177 public enum LoadFailureCodes {
178 LOAD_FAILURE_CODE_NONE, LOAD_FAILURE_CODE_NOT_FOUND, LOAD_FAILURE_CODE_FILE_VERSION_TOO_NEW
181 public boolean load() {
182 final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
183 DocumentBuilder documentBuilder = null;
185 documentBuilder = dbf.newDocumentBuilder();
186 } catch (final ParserConfigurationException e) {
187 // TODO Auto-generated catch block
193 xmlDocument = documentBuilder.parse(fileURI);
194 } catch (final SAXException e) {
195 // TODO Auto-generated catch block
198 } catch (final IOException e) {
199 // TODO Auto-generated catch block
204 final Element rootNode = xmlDocument.getDocumentElement();
205 if (rootNode.getNodeName() != NODE_ROOT) {
206 Log.error("Unexpected XML root node name found: " + rootNode.getNodeName());
210 databaseTitle.setTitleOriginal(rootNode.getAttribute(ATTRIBUTE_TITLE));
212 translationOriginalLocale = rootNode.getAttribute(ATTRIBUTE_TRANSLATION_ORIGINAL_LOCALE);
213 translationAvailableLocales.add(translationOriginalLocale); // Just a cache.
215 isExample = getAttributeAsBoolean(rootNode, ATTRIBUTE_IS_EXAMPLE);
217 final Element nodeConnection = getElementByName(rootNode, NODE_CONNECTION);
218 if (nodeConnection != null) {
219 final String strHostingMode = nodeConnection.getAttribute(ATTRIBUTE_CONNECTION_HOSTING_MODE);
220 if (strHostingMode == "postgres_central") {
221 hostingMode = HostingMode.HOSTING_MODE_POSTGRES_CENTRAL;
222 } else if (strHostingMode == "sqlite") {
223 hostingMode = HostingMode.HOSTING_MODE_SQLITE;
225 hostingMode = HostingMode.HOSTING_MODE_POSTGRES_SELF;
228 connectionServer = nodeConnection.getAttribute(ATTRIBUTE_CONNECTION_SERVER);
229 connectionDatabase = nodeConnection.getAttribute(ATTRIBUTE_CONNECTION_DATABASE);
230 connectionPort = (int) getAttributeAsDecimal(nodeConnection, ATTRIBUTE_CONNECTION_PORT);
233 // We first load the fields, relationships, etc,
235 final List<Node> listTableNodes = getChildrenByTagName(rootNode, NODE_TABLE);
236 for (final Node node : listTableNodes) {
237 if (!(node instanceof Element)) {
241 final Element element = (Element) node;
242 final TableInfo info = loadTableNodeBasic(element);
243 tablesMap.put(info.getName(), info);
246 // We then load the layouts for all tables, because they
247 // need the fields and relationships for all tables:
248 for (final Node node : listTableNodes) {
249 if (!(node instanceof Element)) {
253 final Element element = (Element) node;
254 final String tableName = element.getAttribute(ATTRIBUTE_NAME);
256 // We first load the fields, relationships, etc:
257 final TableInfo info = getTableInfo(tableName);
262 // We then load the layouts afterwards, because they
263 // need the fields and relationships:
264 loadTableLayouts(element, info);
266 tablesMap.put(info.getName(), info);
272 private Element getElementByName(final Element parentElement, final String tagName) {
273 final List<Node> listNodes = getChildrenByTagName(parentElement, tagName);
274 if (listNodes == null) {
278 if (listNodes.size() == 0) {
282 return (Element) listNodes.get(0);
285 private boolean getAttributeAsBoolean(final Element node, final String attributeName) {
286 final String str = node.getAttribute(attributeName);
291 return str.equals("true");
294 private void setAttributeAsBoolean(final Element node, final String attributeName, boolean value) {
295 final String str = value ? "true" : "false";
296 node.setAttribute(attributeName, str);
299 private double getAttributeAsDecimal(final Element node, final String attributeName) {
300 final String str = node.getAttribute(attributeName);
301 if (StringUtils.isEmpty(str)) {
307 value = Double.valueOf(str);
308 } catch (NumberFormatException e) {
309 // e.printStackTrace();
315 private String getStringForDecimal(double value) {
316 final NumberFormat format = NumberFormat.getInstance(Locale.US);
317 format.setGroupingUsed(false); // TODO: Does this change it system-wide?
318 return format.format(value);
321 private void setAttributeAsDecimal(final Element node, final String attributeName, double value) {
322 node.setAttribute(attributeName, getStringForDecimal(value));
326 * Load a title and its translations.
329 * The XML Element that may contain a title attribute and a trans_set of translations of the title.
332 private void loadTitle(final Element node, final Translatable title) {
333 title.setName(node.getAttribute(ATTRIBUTE_NAME));
335 title.setTitleOriginal(node.getAttribute(ATTRIBUTE_TITLE));
337 final Element nodeSet = getElementByName(node, NODE_TRANSLATIONS_SET);
338 if (nodeSet == null) {
342 final List<Node> listNodes = getChildrenByTagName(nodeSet, NODE_TRANSLATIONS);
343 if (listNodes == null) {
347 for (final Node transNode : listNodes) {
348 if (!(transNode instanceof Element)) {
352 final Element element = (Element) transNode;
354 final String locale = element.getAttribute(ATTRIBUTE_TRANSLATION_LOCALE);
355 final String translatedTitle = element.getAttribute(ATTRIBUTE_TRANSLATION_TITLE);
356 if (!StringUtils.isEmpty(locale) && !StringUtils.isEmpty(translatedTitle)) {
357 title.setTitle(translatedTitle, locale);
359 // Remember any new translation locales in our cached list:
360 if (!translationAvailableLocales.contains(locale)) {
361 translationAvailableLocales.add(locale);
367 private void saveTitle(org.w3c.dom.Document doc, Element node, final Translatable title) {
368 node.setAttribute(ATTRIBUTE_NAME, title.getName());
370 node.setAttribute(ATTRIBUTE_TITLE, title.getTitleOriginal());
372 final Element nodeSet = createElement(doc, node, NODE_TRANSLATIONS_SET);
374 for (Entry<String, String> entry : title.getTranslationsMap().entrySet()) {
375 final Element element = createElement(doc, nodeSet, NODE_TRANSLATIONS);
377 element.setAttribute(ATTRIBUTE_TRANSLATION_LOCALE, entry.getKey());
378 element.setAttribute(ATTRIBUTE_TRANSLATION_TITLE, entry.getValue());
386 private TableInfo loadTableNodeBasic(final Element tableNode) {
387 final TableInfo info = new TableInfo();
388 loadTitle(tableNode, info);
389 final String tableName = info.getName();
391 info.isDefault = getAttributeAsBoolean(tableNode, ATTRIBUTE_DEFAULT);
392 info.isHidden = getAttributeAsBoolean(tableNode, ATTRIBUTE_HIDDEN);
394 // These should be loaded before the fields, because the fields use them.
395 final Element relationshipsNode = getElementByName(tableNode, NODE_RELATIONSHIPS);
396 if (relationshipsNode != null) {
397 final List<Node> listNodes = getChildrenByTagName(relationshipsNode, NODE_RELATIONSHIP);
398 for (final Node node : listNodes) {
399 if (!(node instanceof Element)) {
403 final Element element = (Element) node;
404 final Relationship relationship = new Relationship();
405 loadTitle(element, relationship);
406 relationship.setFromTable(tableName);
407 relationship.setFromField(element.getAttribute(ATTRIBUTE_RELATIONSHIP_FROM_FIELD));
408 relationship.setToTable(element.getAttribute(ATTRIBUTE_RELATIONSHIP_TO_TABLE));
409 relationship.setToField(element.getAttribute(ATTRIBUTE_RELATIONSHIP_TO_FIELD));
411 info.relationshipsMap.put(relationship.getName(), relationship);
415 final Element fieldsNode = getElementByName(tableNode, NODE_FIELDS);
416 if (fieldsNode != null) {
417 final List<Node> listNodes = getChildrenByTagName(fieldsNode, NODE_FIELD);
418 for (final Node node : listNodes) {
419 if (!(node instanceof Element)) {
423 final Element element = (Element) node;
424 final Field field = new Field();
425 loadField(element, field);
427 info.fieldsMap.put(field.getName(), field);
431 final Element exampleRowsNode = getElementByName(tableNode, NODE_EXAMPLE_ROWS);
432 if (exampleRowsNode != null) {
434 List<List<DataItem>> exampleRows = new ArrayList<List<DataItem>>();
435 final List<Node> listNodes = getChildrenByTagName(exampleRowsNode, NODE_EXAMPLE_ROW);
436 for (final Node node : listNodes) {
437 if (!(node instanceof Element)) {
441 final Element element = (Element) node;
442 final List<DataItem> row = new ArrayList<DataItem>();
444 final List<Node> listNodesValues = getChildrenByTagName(element, NODE_VALUE);
445 for (final Node nodeValue : listNodesValues) {
446 if (!(nodeValue instanceof Element)) {
450 final Element elementValue = (Element) nodeValue;
451 final String fieldName = elementValue.getAttribute(ATTRIBUTE_COLUMN);
452 if (StringUtils.isEmpty(fieldName)) {
456 DataItem value = null;
457 final Field field = info.fieldsMap.get(fieldName);
459 value = getNodeTextChildAsValue(elementValue, field.getGlomType());
464 exampleRows.add(row);
467 info.exampleRows = exampleRows;
474 * @param elementValue
478 private DataItem getNodeTextChildAsValue(final Element element, GlomFieldType type) {
479 final DataItem result = new DataItem();
481 final String str = element.getTextContent();
483 // Unescape "" to ", because to_file_format() escaped ", as specified by the CSV RFC:
484 String unescaped = "";
485 if (type == GlomFieldType.TYPE_IMAGE) {
486 unescaped = str; // binary data does not have quote characters so we do not bother to escape or unescape it.
488 unescaped = str.replace(QUOTE_FOR_FILE_FORMAT + QUOTE_FOR_FILE_FORMAT, QUOTE_FOR_FILE_FORMAT);
493 final boolean value = (unescaped == "true");
494 result.setBoolean(value);
498 final DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, Locale.ROOT);
501 value = dateFormat.parse(unescaped);
502 } catch (ParseException e) {
503 // e.printStackTrace();
505 result.setDate(value);
509 final byte[] value = null; // TODO.
510 result.setImage(value);
516 value = Double.valueOf(unescaped);
517 } catch (NumberFormatException e) {
518 // e.printStackTrace();
521 result.setNumber(value);
525 result.setText(unescaped);
535 private void setNodeTextChildAsValue(final Element element, final DataItem value, GlomFieldType type) {
540 str = value.getBoolean() ? "true" : "false";
544 // TODO: This is not really the format used by the Glom document:
545 final DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, Locale.ROOT);
546 str = dateFormat.format(value.getDate());
554 str = getStringForDecimal(value.getNumber());
558 str = value.getText();
565 final String escaped = str.replace(QUOTE_FOR_FILE_FORMAT, QUOTE_FOR_FILE_FORMAT + QUOTE_FOR_FILE_FORMAT);
566 element.setTextContent(escaped);
569 private void saveTableNodeBasic(org.w3c.dom.Document doc, final Element tableNode, final TableInfo info) {
570 saveTitle(doc, tableNode, info);
572 setAttributeAsBoolean(tableNode, ATTRIBUTE_DEFAULT, info.isDefault);
573 setAttributeAsBoolean(tableNode, ATTRIBUTE_HIDDEN, info.isHidden);
575 final Element relationshipsNode = createElement(doc, tableNode, NODE_RELATIONSHIPS);
576 for (Relationship relationship : info.relationshipsMap.values()) {
577 final Element element = createElement(doc, relationshipsNode, NODE_RELATIONSHIP);
578 saveTitle(doc, element, relationship);
580 element.setAttribute(ATTRIBUTE_RELATIONSHIP_FROM_FIELD, relationship.getFromField());
581 element.setAttribute(ATTRIBUTE_RELATIONSHIP_TO_TABLE, relationship.getToTable());
582 element.setAttribute(ATTRIBUTE_RELATIONSHIP_TO_FIELD, relationship.getToField());
585 final Element fieldsNode = createElement(doc, tableNode, NODE_FIELDS);
586 for (Field field : info.fieldsMap.values()) {
587 final Element element = createElement(doc, fieldsNode, NODE_FIELD);
588 saveField(doc, element, field);
591 final Element exampleRowsNode = createElement(doc, tableNode, NODE_EXAMPLE_ROWS);
593 for (final List<DataItem> row : info.exampleRows) {
594 final Element node = createElement(doc, exampleRowsNode, NODE_EXAMPLE_ROW);
596 // TODO: This assumes that fieldsMap.values() will have the same sequence as the values,
598 for (final Field field : info.fieldsMap.values()) {
599 if (i < row.size()) {
603 final Element elementValue = createElement(doc, node, NODE_VALUE);
604 elementValue.setAttribute(ATTRIBUTE_COLUMN, field.getName());
606 final DataItem dataItem = row.get(i);
607 setNodeTextChildAsValue(elementValue, dataItem, field.getGlomType());
617 private void saveField(org.w3c.dom.Document doc, Element element, Field field) {
618 saveTitle(doc, element, field);
620 String fieldTypeStr = "";
622 switch (field.getGlomType()) {
624 fieldTypeStr = "Boolean";
627 fieldTypeStr = "Date";
630 fieldTypeStr = "Image";
633 fieldTypeStr = "Number";
636 fieldTypeStr = "Text";
639 fieldTypeStr = "Time";
644 element.setAttribute(ATTRIBUTE_FIELD_TYPE, fieldTypeStr);
646 setAttributeAsBoolean(element, ATTRIBUTE_PRIMARY_KEY, field.getPrimaryKey());
648 final Element elementFormatting = createElement(doc, element, NODE_FORMATTING);
649 saveFormatting(elementFormatting, field.getFormatting());
653 * @param elementFormatting
656 private void saveFormatting(Element element, Formatting formatting) {
657 // formatting.setTextFormatMultiline(getAttributeAsBoolean(elementFormatting, ATTRIBUTE_TEXT_FORMAT_MULTILINE));
659 final NumericFormat numericFormatting = formatting.getNumericFormat();
660 if (numericFormatting != null) {
661 setAttributeAsBoolean(element, ATTRIBUTE_USE_THOUSANDS_SEPARATOR,
662 numericFormatting.getUseThousandsSeparator());
663 setAttributeAsDecimal(element, ATTRIBUTE_DECIMAL_PLACES, numericFormatting.getDecimalPlaces());
671 private void loadTableLayouts(final Element tableNode, final TableInfo info) {
672 final String tableName = info.getName();
674 final Element layoutsNode = getElementByName(tableNode, NODE_DATA_LAYOUTS);
675 if (layoutsNode != null) {
676 final List<Node> listNodes = getChildrenByTagName(layoutsNode, NODE_DATA_LAYOUT);
677 for (final Node node : listNodes) {
678 if (!(node instanceof Element)) {
682 final Element element = (Element) node;
683 final String name = element.getAttribute(ATTRIBUTE_NAME);
684 final List<LayoutGroup> listLayoutGroups = loadLayoutNode(element, tableName);
685 if (name.equals(LAYOUT_NAME_DETAILS)) {
686 info.layoutGroupsDetails = listLayoutGroups;
687 } else if (name.equals(LAYOUT_NAME_LIST)) {
688 info.layoutGroupsList = listLayoutGroups;
690 Log.error("loadTableNode(): unexpected layout name: " + name);
695 final Element reportsNode = getElementByName(tableNode, NODE_REPORTS);
696 if (reportsNode != null) {
697 final List<Node> listNodes = getChildrenByTagName(reportsNode, NODE_REPORT);
698 for (final Node node : listNodes) {
699 if (!(node instanceof Element)) {
703 final Element element = (Element) node;
704 final Report report = new Report();
705 loadReport(element, report, tableName);
707 info.reportsMap.put(report.getName(), report);
716 private List<LayoutGroup> loadLayoutNode(final Element node, final String tableName) {
721 final List<LayoutGroup> result = new ArrayList<LayoutGroup>();
723 final List<Node> listNodes = getChildrenByTagName(node, NODE_DATA_LAYOUT_GROUPS);
724 for (final Node nodeGroups : listNodes) {
725 if (!(nodeGroups instanceof Element)) {
729 final Element elementGroups = (Element) nodeGroups;
731 final NodeList list = elementGroups.getChildNodes();
732 final int num = list.getLength();
733 for (int i = 0; i < num; i++) {
734 final Node nodeLayoutGroup = list.item(i);
735 if (nodeLayoutGroup == null) {
739 if (!(nodeLayoutGroup instanceof Element)) {
743 final Element element = (Element) nodeLayoutGroup;
744 final String tagName = element.getTagName();
745 if (tagName == NODE_DATA_LAYOUT_GROUP) {
746 final LayoutGroup group = new LayoutGroup();
747 loadDataLayoutGroup(element, group, tableName);
749 } else if (tagName == NODE_DATA_LAYOUT_NOTEBOOK) {
750 final LayoutItemNotebook group = new LayoutItemNotebook();
751 loadDataLayoutGroup(element, group, tableName);
753 } else if (tagName == NODE_DATA_LAYOUT_PORTAL) {
754 final LayoutItemPortal portal = new LayoutItemPortal();
755 loadDataLayoutPortal(element, portal, tableName);
769 private void loadUsesRelationship(final Element element, final String tableName, final UsesRelationship item) {
770 if (element == null) {
778 final String relationshipName = element.getAttribute(ATTRIBUTE_RELATIONSHIP_NAME);
779 Relationship relationship = null;
780 if (!StringUtils.isEmpty(relationshipName)) {
781 // std::cout << " debug in : tableName=" << tableName << ", relationshipName=" << relationship_name <<
783 relationship = getRelationship(tableName, relationshipName);
784 item.setRelationship(relationship);
786 if (relationship == null) {
787 Log.error("relationship not found: " + relationshipName + ", in table: " + tableName);
791 // TODO: Unit test loading of doubly-related fields.
792 final String relatedRelationshipName = element.getAttribute(ATTRIBUTE_RELATED_RELATIONSHIP_NAME);
793 if (!StringUtils.isEmpty(relatedRelationshipName) && (relationship != null)) {
794 final Relationship relatedRelationship = getRelationship(relationship.getToTable(), relatedRelationshipName);
795 item.setRelatedRelationship(relatedRelationship);
797 if (relatedRelationship == null) {
798 Log.error("related relationship not found in table=" + relationship.getToTable() + ", name="
799 + relatedRelationshipName);
805 * getElementsByTagName() is recursive, but we do not want that.
811 private List<Node> getChildrenByTagName(final Element parentNode, final String tagName) {
812 final List<Node> result = new ArrayList<Node>();
814 final NodeList list = parentNode.getElementsByTagName(tagName);
815 final int num = list.getLength();
816 for (int i = 0; i < num; i++) {
817 final Node node = list.item(i);
822 final Node itemParentNode = node.getParentNode();
823 if (itemParentNode.equals(parentNode)) {
835 private void loadDataLayoutGroup(final Element nodeGroup, final LayoutGroup group, final String tableName) {
836 loadTitle(nodeGroup, group);
838 // Read the column count:
839 int columnCount = (int) getAttributeAsDecimal(nodeGroup, ATTRIBUTE_LAYOUT_GROUP_COLUMNS_COUNT);
840 if (columnCount < 1) {
841 columnCount = 1; // 0 is a useless default.
843 group.setColumnCount(columnCount);
845 // Get the child items:
846 final NodeList listNodes = nodeGroup.getChildNodes();
847 final int num = listNodes.getLength();
848 for (int i = 0; i < num; i++) {
849 final Node node = listNodes.item(i);
850 if (!(node instanceof Element)) {
854 final Element element = (Element) node;
855 final String tagName = element.getTagName();
856 if (tagName == NODE_DATA_LAYOUT_GROUP) {
857 final LayoutGroup childGroup = new LayoutGroup();
858 loadDataLayoutGroup(element, childGroup, tableName);
859 group.addItem(childGroup);
860 } else if (tagName == NODE_DATA_LAYOUT_NOTEBOOK) {
861 final LayoutItemNotebook childGroup = new LayoutItemNotebook();
862 loadDataLayoutGroup(element, childGroup, tableName);
863 group.addItem(childGroup);
864 } else if (tagName == NODE_DATA_LAYOUT_PORTAL) {
865 final LayoutItemPortal childGroup = new LayoutItemPortal();
866 loadDataLayoutPortal(element, childGroup, tableName);
867 group.addItem(childGroup);
868 } else if (element.getTagName() == NODE_DATA_LAYOUT_ITEM) {
869 final LayoutItemField item = new LayoutItemField();
870 loadDataLayoutItemField(element, item, tableName);
872 } else if (element.getTagName() == NODE_DATA_LAYOUT_ITEM_GROUPBY) {
873 final LayoutItemGroupBy item = new LayoutItemGroupBy();
874 loadDataLayoutItemGroupBy(element, item, tableName);
885 private void loadDataLayoutItemGroupBy(final Element element, final LayoutItemGroupBy item, final String tableName) {
886 loadDataLayoutGroup(element, item, tableName);
888 final Element elementGroupBy = getElementByName(element, NODE_GROUPBY);
889 if (elementGroupBy == null) {
893 final LayoutItemField fieldGroupBy = new LayoutItemField();
894 loadDataLayoutItemField(elementGroupBy, fieldGroupBy, tableName);
895 item.setFieldGroupBy(fieldGroupBy);
897 final Element elementSecondaryFields = getElementByName(element, NODE_SECONDARY_FIELDS);
898 if (elementSecondaryFields == null) {
902 final Element elementLayoutGroup = getElementByName(elementSecondaryFields, NODE_DATA_LAYOUT_GROUP);
903 if (elementLayoutGroup != null) {
904 final LayoutGroup secondaryLayoutGroup = new LayoutGroup();
905 loadDataLayoutGroup(elementLayoutGroup, secondaryLayoutGroup, tableName);
906 item.setSecondaryFields(secondaryLayoutGroup);
914 private void loadDataLayoutItemField(final Element element, final LayoutItemField item, final String tableName) {
915 item.setName(element.getAttribute(ATTRIBUTE_NAME));
916 loadUsesRelationship(element, tableName, item);
918 final Element elementCustomTitle = getElementByName(element, NODE_CUSTOM_TITLE);
919 if (elementCustomTitle != null) {
920 final CustomTitle customTitle = item.getCustomTitle();
921 customTitle.setUseCustomTitle(getAttributeAsBoolean(elementCustomTitle, ATTRIBUTE_CUSTOM_TITLE_USE_CUSTOM));
922 loadTitle(elementCustomTitle, customTitle); // LayoutItemField doesn't use its own title member.
925 // Get the actual field:
926 final String fieldName = item.getName();
927 final String inTableName = item.getTableUsed(tableName);
928 final Field field = getField(inTableName, fieldName);
929 item.setFullFieldDetails(field);
931 item.setUseDefaultFormatting(getAttributeAsBoolean(element, ATTRIBUTE_USE_DEFAULT_FORMATTING));
933 final Element elementFormatting = getElementByName(element, NODE_FORMATTING);
934 if (elementFormatting != null) {
935 loadFormatting(elementFormatting, item.getFormatting());
943 private void loadDataLayoutPortal(final Element element, final LayoutItemPortal portal, final String tableName) {
944 loadUsesRelationship(element, tableName, portal);
945 final String relatedTableName = portal.getTableUsed(tableName);
946 loadDataLayoutGroup(element, portal, relatedTableName);
948 final Element elementNavigation = getElementByName(element, NODE_DATA_LAYOUT_PORTAL_NAVIGATIONRELATIONSHIP);
949 if (elementNavigation != null) {
950 final String navigationTypeAsString = elementNavigation.getAttribute(ATTRIBUTE_PORTAL_NAVIGATION_TYPE);
951 if (StringUtils.isEmpty(navigationTypeAsString)
952 || navigationTypeAsString == ATTRIBUTE_PORTAL_NAVIGATION_TYPE_AUTOMATIC) {
953 portal.setNavigationType(LayoutItemPortal.NavigationType.NAVIGATION_AUTOMATIC);
954 } else if (navigationTypeAsString == ATTRIBUTE_PORTAL_NAVIGATION_TYPE_NONE) {
955 portal.setNavigationType(LayoutItemPortal.NavigationType.NAVIGATION_NONE);
956 } else if (navigationTypeAsString == ATTRIBUTE_PORTAL_NAVIGATION_TYPE_SPECIFIC) {
957 // Read the specified relationship name:
958 final UsesRelationship relationshipNavigationSpecific = new UsesRelationshipImpl();
959 loadUsesRelationship(elementNavigation, relatedTableName, relationshipNavigationSpecific);
960 portal.setNavigationRelationshipSpecific(relationshipNavigationSpecific);
970 private void loadField(final Element element, final Field field) {
971 loadTitle(element, field);
973 Field.GlomFieldType fieldType = Field.GlomFieldType.TYPE_INVALID;
974 final String fieldTypeStr = element.getAttribute(ATTRIBUTE_FIELD_TYPE);
975 if (!StringUtils.isEmpty(fieldTypeStr)) {
976 if (fieldTypeStr.equals("Boolean")) {
977 fieldType = Field.GlomFieldType.TYPE_BOOLEAN;
978 } else if (fieldTypeStr.equals("Date")) {
979 fieldType = Field.GlomFieldType.TYPE_DATE;
980 } else if (fieldTypeStr.equals("Image")) {
981 fieldType = Field.GlomFieldType.TYPE_IMAGE;
982 } else if (fieldTypeStr.equals("Number")) {
983 fieldType = Field.GlomFieldType.TYPE_NUMERIC;
984 } else if (fieldTypeStr.equals("Text")) {
985 fieldType = Field.GlomFieldType.TYPE_TEXT;
986 } else if (fieldTypeStr.equals("Time")) {
987 fieldType = Field.GlomFieldType.TYPE_TIME;
991 field.setGlomFieldType(fieldType);
993 field.setPrimaryKey(getAttributeAsBoolean(element, ATTRIBUTE_PRIMARY_KEY));
995 final Element elementFormatting = getElementByName(element, NODE_FORMATTING);
996 if (elementFormatting != null) {
997 loadFormatting(elementFormatting, field.getFormatting());
1002 * @param elementFormatting
1005 private void loadFormatting(final Element elementFormatting, final Formatting formatting) {
1006 if (elementFormatting == null) {
1010 if (formatting == null) {
1014 // formatting.setTextFormatMultiline(getAttributeAsBoolean(elementFormatting, ATTRIBUTE_TEXT_FORMAT_MULTILINE));
1016 final NumericFormat numericFormatting = formatting.getNumericFormat();
1017 if (numericFormatting != null) {
1018 numericFormatting.setUseThousandsSeparator(getAttributeAsBoolean(elementFormatting,
1019 ATTRIBUTE_USE_THOUSANDS_SEPARATOR));
1021 .setDecimalPlaces((int) getAttributeAsDecimal(elementFormatting, ATTRIBUTE_DECIMAL_PLACES));
1030 private void loadReport(final Element element, final Report report, final String tableName) {
1031 report.setName(element.getAttribute(ATTRIBUTE_NAME));
1032 loadTitle(element, report);
1034 final List<LayoutGroup> listLayoutGroups = loadLayoutNode(element, tableName);
1036 // A report can actually only have one LayoutGroup,
1037 // though it uses the same XML structure as List and Details layouts,
1038 // which (wrongly) suggests that it can have more than one group.
1039 LayoutGroup layoutGroup = null;
1040 if (!listLayoutGroups.isEmpty()) {
1041 layoutGroup = listLayoutGroups.get(0);
1044 report.setLayoutGroup(layoutGroup);
1047 private TableInfo getTableInfo(final String tableName) {
1048 return tablesMap.get(tableName);
1051 public enum HostingMode {
1052 HOSTING_MODE_POSTGRES_CENTRAL, HOSTING_MODE_POSTGRES_SELF, HOSTING_MODE_SQLITE
1055 public String getDatabaseTitle(final String locale) {
1056 return databaseTitle.getTitle(locale);
1059 public String getDatabaseTitleOriginal() {
1060 return databaseTitle.getTitleOriginal();
1063 public List<String> getTranslationAvailableLocales() {
1064 return translationAvailableLocales;
1067 public Document.HostingMode getHostingMode() {
1072 * @param hostingMode
1074 public void setHostingMode(HostingMode hostingMode) {
1075 this.hostingMode = hostingMode;
1078 public String getConnectionServer() {
1079 return connectionServer;
1082 public int getConnectionPort() {
1083 return connectionPort;
1086 public void setConnectionPort(int port) {
1087 connectionPort = port;
1090 public String getConnectionDatabase() {
1091 return connectionDatabase;
1094 public List<String> getTableNames() {
1095 // TODO: Return a Set?
1096 return new ArrayList<String>(tablesMap.keySet());
1099 public boolean getTableIsHidden(final String tableName) {
1100 final TableInfo info = getTableInfo(tableName);
1105 return info.isHidden;
1108 public String getTableTitle(final String tableName, final String locale) {
1109 final TableInfo info = getTableInfo(tableName);
1114 return info.getTitle(locale);
1117 public List<List<DataItem>> getExampleRows(final String tableName) {
1118 final TableInfo info = getTableInfo(tableName);
1123 return info.exampleRows;
1126 public String getDefaultTable() {
1127 for (final TableInfo info : tablesMap.values()) {
1128 if (info.isDefault) {
1129 return info.getName();
1136 public boolean getTableIsKnown(final String tableName) {
1137 final TableInfo info = getTableInfo(tableName);
1145 public List<Field> getTableFields(final String tableName) {
1146 final TableInfo info = getTableInfo(tableName);
1151 return new ArrayList<Field>(info.fieldsMap.values());
1154 public Field getField(final String tableName, final String strFieldName) {
1155 final TableInfo info = getTableInfo(tableName);
1160 return info.fieldsMap.get(strFieldName);
1163 public List<LayoutGroup> getDataLayoutGroups(final String layoutName, final String parentTableName) {
1164 final TableInfo info = getTableInfo(parentTableName);
1166 return new ArrayList<LayoutGroup>();
1169 if (layoutName == LAYOUT_NAME_DETAILS) {
1170 return info.layoutGroupsDetails;
1171 } else if (layoutName == LAYOUT_NAME_LIST) {
1172 return info.layoutGroupsList;
1174 return new ArrayList<LayoutGroup>();
1178 public List<String> getReportNames(final String tableName) {
1179 final TableInfo info = getTableInfo(tableName);
1181 return new ArrayList<String>();
1184 return new ArrayList<String>(info.reportsMap.keySet());
1187 public Report getReport(final String tableName, final String reportName) {
1188 final TableInfo info = getTableInfo(tableName);
1193 return info.reportsMap.get(reportName);
1201 public Relationship getFieldUsedInRelationshipToOne(final String tableName, final LayoutItemField layoutField) {
1203 if (layoutField == null) {
1204 Log.error("layoutField was null");
1208 Relationship result = null;
1210 final String tableUsed = layoutField.getTableUsed(tableName);
1211 final TableInfo info = getTableInfo(tableUsed);
1213 // This table is special. We would not create a relationship to it using a field:
1214 // if(tableUsed == GLOM_STANDARD_TABLE_PREFS_TABLE_NAME)
1217 Log.error("table not found: " + tableUsed);
1221 // Look at each relationship:
1222 final String fieldName = layoutField.getName();
1223 for (final Relationship relationship : info.relationshipsMap.values()) {
1224 if (relationship != null) {
1225 // If the relationship uses the field
1226 if (StringUtils.equals(relationship.getFromField(), fieldName)) {
1227 // if the to_table is not hidden:
1228 if (!getTableIsHidden(relationship.getToTable())) {
1229 // TODO_Performance: The use of this convenience method means we get the full relationship
1230 // information again:
1231 if (getRelationshipIsToOne(tableName, relationship.getName())) {
1232 result = relationship;
1244 * @param relationshipName
1247 private boolean getRelationshipIsToOne(final String tableName, final String relationshipName) {
1248 final Relationship relationship = getRelationship(tableName, relationshipName);
1249 if (relationship != null) {
1250 final Field fieldTo = getField(relationship.getToTable(), relationship.getToField());
1251 if (fieldTo != null) {
1252 return (fieldTo.getPrimaryKey() || fieldTo.getUniqueKey());
1261 * @param relationshipName
1264 private Relationship getRelationship(final String tableName, final String relationshipName) {
1265 final TableInfo info = getTableInfo(tableName);
1267 Log.error("table not found: " + tableName);
1271 return info.relationshipsMap.get(relationshipName);
1277 * @param relationship
1281 public TableToViewDetails getPortalSuitableTableToViewDetails(final LayoutItemPortal portal) {
1282 UsesRelationship navigationRelationship = null;
1284 // Check whether a relationship was specified:
1285 if (portal.getNavigationType() == NavigationType.NAVIGATION_AUTOMATIC) {
1286 navigationRelationship = getPortalNavigationRelationshipAutomatic(portal);
1288 navigationRelationship = portal.getNavigationRelationshipSpecific();
1291 // Get the navigation table name from the chosen relationship:
1292 final String directlyRelatedTableName = portal.getTableUsed("" /* not relevant */);
1294 // The navigation_table_name (and therefore, the table_name output parameter,
1295 // as well) stays empty if the navrel type was set to none.
1296 String navigationTableName = null;
1297 if (navigationRelationship != null) {
1298 navigationTableName = navigationRelationship.getTableUsed(directlyRelatedTableName);
1299 } else if (portal.getNavigationType() != NavigationType.NAVIGATION_NONE) {
1300 // An empty result from get_portal_navigation_relationship_automatic() or
1301 // get_navigation_relationship_specific() means we should use the directly related table:
1302 navigationTableName = directlyRelatedTableName;
1305 if (StringUtils.isEmpty(navigationTableName)) {
1310 Log.error("document is null.");
1314 if (getTableIsHidden(navigationTableName)) {
1315 Log.error("navigation_table_name indicates a hidden table: " + navigationTableName);
1319 final TableToViewDetails result = new TableToViewDetails();
1320 result.tableName = navigationTableName;
1321 result.usesRelationship = navigationRelationship;
1330 private UsesRelationship getPortalNavigationRelationshipAutomatic(final LayoutItemPortal portal) {
1335 // If the related table is not hidden then we can just navigate to that:
1336 final String direct_related_table_name = portal.getTableUsed("" /* parent table - not relevant */);
1337 if (!getTableIsHidden(direct_related_table_name)) {
1338 // Non-hidden tables can just be shown directly. Navigate to it:
1341 // If the related table is hidden,
1342 // then find a suitable related non-hidden table by finding the first layout field that mentions one:
1343 final LayoutItemField field = getPortalFieldIsFromNonHiddenRelatedRecord(portal);
1344 if (field != null) {
1345 return field; // Returns the UsesRelationship base part. (A relationship belonging to the portal's
1348 // Instead, find a key field that's used in a relationship,
1349 // and pretend that we are showing the to field as a related field:
1350 final Relationship fieldIndentifies = getPortalFieldIdentifiesNonHiddenRelatedRecord(portal);
1351 if (fieldIndentifies != null) {
1352 final UsesRelationship result = new UsesRelationshipImpl();
1353 result.setRelationship(fieldIndentifies);
1359 // There was no suitable related table to show:
1368 private LayoutItemField getPortalFieldIsFromNonHiddenRelatedRecord(final LayoutItemPortal portal) {
1369 // Find the first field that is from a non-hidden related table.
1375 final LayoutItemField result = null;
1377 final String parent_table_name = portal.getTableUsed("" /* parent table - not relevant */);
1379 final List<LayoutItem> items = portal.getItems();
1380 for (final LayoutItem item : items) {
1381 if (item instanceof LayoutItemField) {
1382 final LayoutItemField field = (LayoutItemField) item;
1383 if (field.getHasRelationshipName()) {
1384 final String table_name = field.getTableUsed(parent_table_name);
1385 if (!(getTableIsHidden(table_name))) {
1396 * @param used_in_relationship
1401 private Relationship getPortalFieldIdentifiesNonHiddenRelatedRecord(final LayoutItemPortal portal) {
1402 // Find the first field that is from a non-hidden related table.
1405 Log.error("document is null");
1409 final String parent_table_name = portal.getTableUsed("" /* parent table - not relevant */);
1411 final List<LayoutItem> items = portal.getItems();
1412 for (final LayoutItem item : items) {
1413 if (item instanceof LayoutItemField) {
1414 final LayoutItemField field = (LayoutItemField) item;
1415 if (field.getHasRelationshipName()) {
1416 final Relationship relationship = getFieldUsedInRelationshipToOne(parent_table_name, field);
1417 if (relationship != null) {
1418 final String table_name = relationship.getToTable();
1419 if (!StringUtils.isEmpty(table_name)) {
1420 if (!(getTableIsHidden(table_name))) {
1421 return relationship;
1435 * @return The destination table name for navigation.
1437 public String getLayoutItemFieldShouldHaveNavigation(final String tableName, final LayoutItemField layoutItem) {
1438 if (StringUtils.isEmpty(tableName)) {
1442 if (layoutItem == null) {
1446 // Check whether the field controls a relationship,
1447 // meaning it identifies a record in another table.
1448 final Relationship fieldUsedInRelationshipToOne = getFieldUsedInRelationshipToOne(tableName, layoutItem);
1449 if (fieldUsedInRelationshipToOne != null) {
1450 return fieldUsedInRelationshipToOne.getToTable();
1453 // Check whether the field identifies a record in another table
1454 // just because it is a primary key in that table:
1455 final Field fieldInfo = layoutItem.getFullFieldDetails();
1456 final boolean fieldIsRelatedPrimaryKey = layoutItem.getHasRelationshipName() && (fieldInfo != null)
1457 && fieldInfo.getPrimaryKey();
1458 if (fieldIsRelatedPrimaryKey) {
1459 return layoutItem.getRelationship().getToTable();
1468 public void setIsExampleFile(boolean isExample) {
1469 this.isExample = isExample;
1474 public boolean getIsExampleFile() {
1481 public boolean save() {
1482 final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
1483 DocumentBuilder documentBuilder = null;
1485 documentBuilder = dbf.newDocumentBuilder();
1486 } catch (final ParserConfigurationException e) {
1487 // TODO Auto-generated catch block
1488 e.printStackTrace();
1492 final org.w3c.dom.Document doc = documentBuilder.newDocument();
1493 final Element rootNode = doc.createElement(NODE_ROOT);
1494 doc.appendChild(rootNode);
1496 rootNode.setAttribute(ATTRIBUTE_TITLE, databaseTitle.getTitleOriginal());
1497 rootNode.setAttribute(ATTRIBUTE_TRANSLATION_ORIGINAL_LOCALE, translationOriginalLocale);
1498 setAttributeAsBoolean(rootNode, ATTRIBUTE_IS_EXAMPLE, isExample);
1500 String strHostingMode = "";
1501 if (hostingMode == HostingMode.HOSTING_MODE_POSTGRES_CENTRAL) {
1502 strHostingMode = "postgres_central";
1503 } else if (hostingMode == HostingMode.HOSTING_MODE_SQLITE) {
1504 strHostingMode = "sqlite";
1506 strHostingMode = "postgres_self";
1508 final Element nodeConnection = createElement(doc, rootNode, NODE_CONNECTION);
1509 nodeConnection.setAttribute(ATTRIBUTE_CONNECTION_HOSTING_MODE, strHostingMode);
1510 nodeConnection.setAttribute(ATTRIBUTE_CONNECTION_SERVER, connectionServer);
1511 nodeConnection.setAttribute(ATTRIBUTE_CONNECTION_DATABASE, connectionDatabase);
1512 setAttributeAsDecimal(nodeConnection, ATTRIBUTE_CONNECTION_PORT, connectionPort);
1515 for (TableInfo table : tablesMap.values()) {
1516 final Element nodeTable = createElement(doc, rootNode, NODE_TABLE);
1517 saveTableNodeBasic(doc, nodeTable, table);
1518 saveTableLayouts(doc, nodeTable, table);
1521 TransformerFactory transformerFactory = TransformerFactory.newInstance();
1522 Transformer transformer;
1524 transformer = transformerFactory.newTransformer();
1525 } catch (TransformerConfigurationException e) {
1526 // TODO Auto-generated catch block
1527 e.printStackTrace();
1531 // TODO: This probably distorts text nodes,
1532 // so careful when we load/save them. For instance, scripts.
1533 transformer.setOutputProperty(OutputKeys.INDENT, "yes");
1534 transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
1536 // Make sure that the parent directory exists:
1537 final File file = new File(fileURI);
1539 Files.createParentDirs(file);
1540 } catch (IOException e) {
1541 e.printStackTrace();
1545 DOMSource source = new DOMSource(doc);
1546 StreamResult result = new StreamResult(file);
1548 // Output to console for testing
1549 // StreamResult result = new StreamResult(System.out);
1552 transformer.transform(source, result);
1553 } catch (TransformerException e) {
1554 // TODO Auto-generated catch block
1555 e.printStackTrace();
1567 private void saveTableLayouts(org.w3c.dom.Document doc, Element tableNode, TableInfo table) {
1569 final Element layoutsNode = createElement(doc, tableNode, NODE_DATA_LAYOUTS);
1571 final Element nodeLayoutDetails = createElement(doc, layoutsNode, NODE_DATA_LAYOUT);
1572 nodeLayoutDetails.setAttribute(ATTRIBUTE_NAME, LAYOUT_NAME_DETAILS);
1573 saveLayoutNode(doc, nodeLayoutDetails, table.layoutGroupsDetails);
1575 final Element nodeLayoutList = createElement(doc, layoutsNode, NODE_DATA_LAYOUT);
1576 nodeLayoutList.setAttribute(ATTRIBUTE_NAME, LAYOUT_NAME_LIST);
1577 saveLayoutNode(doc, nodeLayoutList, table.layoutGroupsList);
1579 final Element reportsNode = createElement(doc, tableNode, NODE_REPORTS);
1580 for (Report report : table.reportsMap.values()) {
1581 final Element element = createElement(doc, reportsNode, NODE_REPORT);
1582 saveReport(doc, element, report);
1592 private void saveReport(final org.w3c.dom.Document doc, final Element element, final Report report) {
1593 // TODO Auto-generated method stub
1597 private void saveLayoutNode(final org.w3c.dom.Document doc, Element element, final List<LayoutGroup> layoutGroups) {
1598 final Element elementGroups = createElement(doc, element, NODE_DATA_LAYOUT_GROUPS);
1600 for (final LayoutGroup layoutGroup : layoutGroups) {
1601 if (layoutGroup instanceof LayoutItemNotebook) {
1602 final Element elementGroup = createElement(doc, elementGroups, NODE_DATA_LAYOUT_NOTEBOOK);
1603 saveDataLayoutGroup(doc, elementGroup, layoutGroup);
1604 } else if (layoutGroup instanceof LayoutItemPortal) {
1605 final Element elementGroup = createElement(doc, elementGroups, NODE_DATA_LAYOUT_PORTAL);
1606 saveDataLayoutPortal(doc, elementGroup, (LayoutItemPortal) layoutGroup);
1608 final Element elementGroup = createElement(doc, elementGroups, NODE_DATA_LAYOUT_GROUP);
1609 saveDataLayoutGroup(doc, elementGroup, layoutGroup);
1617 * @param elementGroup
1618 * @param layoutGroup
1620 private void saveDataLayoutPortal(final org.w3c.dom.Document doc, final Element element,
1621 final LayoutItemPortal portal) {
1622 saveUsesRelationship(element, portal);
1623 saveDataLayoutGroup(doc, element, portal);
1625 final Element elementNavigation = createElement(doc, element, NODE_DATA_LAYOUT_PORTAL_NAVIGATIONRELATIONSHIP);
1626 String navigationTypeAsString = "";
1627 switch (portal.getNavigationType()) {
1628 case NAVIGATION_AUTOMATIC:
1629 navigationTypeAsString = ATTRIBUTE_PORTAL_NAVIGATION_TYPE_AUTOMATIC;
1631 case NAVIGATION_NONE:
1632 navigationTypeAsString = ATTRIBUTE_PORTAL_NAVIGATION_TYPE_NONE;
1634 case NAVIGATION_SPECIFIC:
1635 navigationTypeAsString = ATTRIBUTE_PORTAL_NAVIGATION_TYPE_SPECIFIC;
1640 elementNavigation.setAttribute(ATTRIBUTE_PORTAL_NAVIGATION_TYPE, navigationTypeAsString);
1642 if (navigationTypeAsString == ATTRIBUTE_PORTAL_NAVIGATION_TYPE_SPECIFIC) {
1643 // Write the specified relationship name:
1644 saveUsesRelationship(elementNavigation, portal.getNavigationRelationshipSpecific());
1650 * @param elementGroup
1651 * @param layoutGroup
1653 private void saveDataLayoutGroup(final org.w3c.dom.Document doc, final Element nodeGroup, final LayoutGroup group) {
1654 saveTitle(doc, nodeGroup, group);
1656 // Write the column count:
1657 setAttributeAsDecimal(nodeGroup, ATTRIBUTE_LAYOUT_GROUP_COLUMNS_COUNT, group.getColumnCount());
1659 // Write the child items:
1660 for (LayoutItem layoutItem : group.getItems()) {
1661 if (layoutItem instanceof LayoutItemPortal) {
1662 final Element element = createElement(doc, nodeGroup, NODE_DATA_LAYOUT_PORTAL);
1663 saveDataLayoutPortal(doc, element, (LayoutItemPortal) layoutItem);
1664 } else if (layoutItem instanceof LayoutItemNotebook) {
1665 final Element element = createElement(doc, nodeGroup, NODE_DATA_LAYOUT_NOTEBOOK);
1666 saveDataLayoutGroup(doc, element, (LayoutItemNotebook) layoutItem);
1667 } else if (layoutItem instanceof LayoutGroup) {
1668 final Element element = createElement(doc, nodeGroup, NODE_DATA_LAYOUT_GROUP);
1669 saveDataLayoutGroup(doc, element, (LayoutGroup) layoutItem);
1670 } else if (layoutItem instanceof LayoutItemField) {
1671 final Element element = createElement(doc, nodeGroup, NODE_DATA_LAYOUT_ITEM);
1672 saveDataLayoutItemField(doc, element, (LayoutItemField) layoutItem);
1673 } else if (layoutItem instanceof LayoutItemGroupBy) {
1674 final Element element = createElement(doc, nodeGroup, NODE_DATA_LAYOUT_ITEM_GROUPBY);
1675 saveDataLayoutItemGroupBy(doc, element, (LayoutItemGroupBy) layoutItem);
1685 private void saveDataLayoutItemGroupBy(org.w3c.dom.Document doc, Element element, LayoutItemGroupBy item) {
1686 saveDataLayoutGroup(doc, element, item);
1688 final Element elementGroupBy = createElement(doc, element, NODE_GROUPBY);
1689 saveDataLayoutItemField(doc, elementGroupBy, item.getFieldGroupBy());
1691 final Element elementSecondaryFields = createElement(doc, element, NODE_SECONDARY_FIELDS);
1692 final Element elementLayoutGroup = createElement(doc, elementSecondaryFields, NODE_DATA_LAYOUT_GROUP);
1693 saveDataLayoutGroup(doc, elementLayoutGroup, item.getSecondaryFields());
1701 private void saveDataLayoutItemField(final org.w3c.dom.Document doc, final Element element,
1702 final LayoutItemField item) {
1703 element.setAttribute(ATTRIBUTE_NAME, item.getName());
1704 saveUsesRelationship(element, item);
1706 final CustomTitle customTitle = item.getCustomTitle();
1707 if (customTitle != null) {
1708 final Element elementCustomTitle = createElement(doc, element, NODE_CUSTOM_TITLE);
1709 setAttributeAsBoolean(elementCustomTitle, ATTRIBUTE_CUSTOM_TITLE_USE_CUSTOM,
1710 customTitle.getUseCustomTitle());
1711 saveTitle(doc, elementCustomTitle, customTitle); // LayoutItemField doesn't use its own title member.
1714 setAttributeAsBoolean(element, ATTRIBUTE_USE_DEFAULT_FORMATTING, item.getUseDefaultFormatting());
1716 final Element elementFormatting = createElement(doc, element, NODE_FORMATTING);
1717 saveFormatting(elementFormatting, item.getFormatting());
1724 private void saveUsesRelationship(Element element, UsesRelationship item) {
1725 final Relationship relationship = item.getRelationship();
1726 if (relationship != null) {
1727 element.setAttribute(ATTRIBUTE_RELATIONSHIP_NAME, relationship.getName());
1730 final Relationship relatedRelationship = item.getRelatedRelationship();
1731 if (relatedRelationship != null) {
1732 element.setAttribute(ATTRIBUTE_RELATED_RELATIONSHIP_NAME, relatedRelationship.getName());
1738 * @param nodeConnection
1741 private Element createElement(final org.w3c.dom.Document doc, final Element parentNode, final String name) {
1742 Element node = doc.createElement(name);
1743 parentNode.appendChild(node);
1747 public String getSelfHostedDirectoryPath() {
1748 final String uriFile = getFileURI();
1749 if (!StringUtils.isEmpty(uriFile)) {
1750 final File file = new File(uriFile);
1751 final File parent = file.getParentFile();
1752 if (parent == null) {
1757 File dataDir = null;
1758 switch (hostingMode) {
1759 case HOSTING_MODE_POSTGRES_SELF:
1760 dataDir = new File(parent, "glom_postgres_data");
1762 case HOSTING_MODE_POSTGRES_CENTRAL:
1765 case HOSTING_MODE_SQLITE:
1773 if (dataDir != null) {
1774 return dataDir.getPath();
1778 // TODO: std::cerr << G_STRFUNC << ": returning empty string." << std::endl;
1784 public void setConnectionDatabase(String databaseName) {
1785 connectionDatabase = databaseName;