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.ByteArrayInputStream;
24 import java.io.IOException;
25 //import java.io.InputStream;
26 //import java.net.URLConnection;
27 import java.text.DateFormat;
28 import java.text.NumberFormat;
29 import java.text.ParseException;
30 import java.util.ArrayList;
31 import java.util.Date;
32 import java.util.HashMap;
33 import java.util.Hashtable;
34 import java.util.List;
35 import java.util.Locale;
37 import java.util.Map.Entry;
39 import javax.xml.parsers.DocumentBuilder;
40 import javax.xml.parsers.DocumentBuilderFactory;
41 import javax.xml.parsers.ParserConfigurationException;
42 import javax.xml.transform.OutputKeys;
43 import javax.xml.transform.Transformer;
44 import javax.xml.transform.TransformerConfigurationException;
45 import javax.xml.transform.TransformerException;
46 import javax.xml.transform.TransformerFactory;
47 import javax.xml.transform.dom.DOMSource;
48 import javax.xml.transform.stream.StreamResult;
50 import org.apache.commons.lang3.StringUtils;
51 import org.glom.web.server.Utils;
52 import org.glom.web.shared.DataItem;
53 import org.glom.web.shared.libglom.CustomTitle;
54 import org.glom.web.shared.libglom.Field;
55 import org.glom.web.shared.libglom.Field.GlomFieldType;
56 import org.glom.web.shared.libglom.NumericFormat;
57 import org.glom.web.shared.libglom.Relationship;
58 import org.glom.web.shared.libglom.Report;
59 import org.glom.web.shared.libglom.Translatable;
60 import org.glom.web.shared.libglom.layout.Formatting;
61 import org.glom.web.shared.libglom.layout.LayoutGroup;
62 import org.glom.web.shared.libglom.layout.LayoutItem;
63 import org.glom.web.shared.libglom.layout.LayoutItemField;
64 import org.glom.web.shared.libglom.layout.LayoutItemNotebook;
65 import org.glom.web.shared.libglom.layout.LayoutItemPortal;
66 import org.glom.web.shared.libglom.layout.LayoutItemPortal.NavigationType;
67 import org.glom.web.shared.libglom.layout.TableToViewDetails;
68 import org.glom.web.shared.libglom.layout.UsesRelationship;
69 import org.glom.web.shared.libglom.layout.UsesRelationshipImpl;
70 import org.glom.web.shared.libglom.layout.reportparts.LayoutItemGroupBy;
71 import org.jfree.util.Log;
72 import org.w3c.dom.Element;
73 import org.w3c.dom.Node;
74 import org.w3c.dom.NodeList;
75 import org.xml.sax.SAXException;
77 import com.google.common.io.Files;
80 * @author Murray Cumming <murrayc@openismus.com>
83 public class Document {
85 @SuppressWarnings("serial")
86 private static class TableInfo extends Translatable {
87 private boolean isDefault;
88 private boolean isHidden;
90 private final Hashtable<String, Field> fieldsMap = new Hashtable<String, Field>();
91 private final Hashtable<String, Relationship> relationshipsMap = new Hashtable<String, Relationship>();
92 private final Hashtable<String, Report> reportsMap = new Hashtable<String, Report>();
94 private List<LayoutGroup> layoutGroupsList = new ArrayList<LayoutGroup>();
95 private List<LayoutGroup> layoutGroupsDetails = new ArrayList<LayoutGroup>();
97 // A list of maps (field name to value).
98 private List<Map<String, DataItem>> exampleRows = null;
101 private String fileURI = "";
102 private org.w3c.dom.Document xmlDocument = null;
104 private final Translatable databaseTitle = new Translatable();
105 private String translationOriginalLocale = "";
106 private final List<String> translationAvailableLocales = new ArrayList<String>();
107 private boolean isExample = false;
108 private HostingMode hostingMode = HostingMode.HOSTING_MODE_POSTGRES_CENTRAL;
109 private String connectionServer = "";
110 private String connectionDatabase = "";
111 private int connectionPort = 0;
112 private final Hashtable<String, TableInfo> tablesMap = new Hashtable<String, TableInfo>();
114 private static final String NODE_ROOT = "glom_document";
115 private static final String ATTRIBUTE_IS_EXAMPLE = "is_example";
116 private static final String ATTRIBUTE_TRANSLATION_ORIGINAL_LOCALE = "translation_original_locale";
117 private static final String NODE_CONNECTION = "connection";
118 private static final String ATTRIBUTE_CONNECTION_HOSTING_MODE = "hosting_mode";
119 private static final String ATTRIBUTE_CONNECTION_SERVER = "server";
120 private static final String ATTRIBUTE_CONNECTION_DATABASE = "database";
121 private static final String ATTRIBUTE_CONNECTION_PORT = "port";
122 private static final String NODE_TABLE = "table";
123 private static final String ATTRIBUTE_NAME = "name";
124 private static final String ATTRIBUTE_TITLE = "title";
125 private static final String DEPRECATED_ATTRIBUTE_DATABASE_TITLE = "database_title";
126 private static final String ATTRIBUTE_DEFAULT = "default";
127 private static final String ATTRIBUTE_HIDDEN = "hidden";
128 private static final String NODE_TRANSLATIONS_SET = "trans_set";
129 private static final String NODE_TRANSLATIONS = "trans";
130 private static final String ATTRIBUTE_TRANSLATION_LOCALE = "loc";
131 private static final String ATTRIBUTE_TRANSLATION_TITLE = "val";
132 private static final String NODE_REPORTS = "reports";
133 private static final String NODE_REPORT = "report";
134 private static final String NODE_FIELDS = "fields";
135 private static final String NODE_FIELD = "field";
136 private static final String NODE_EXAMPLE_ROWS = "example_rows";
137 private static final String NODE_EXAMPLE_ROW = "example_row";
138 private static final String NODE_VALUE = "value";
139 private static final String ATTRIBUTE_COLUMN = "column";
140 private static final String ATTRIBUTE_PRIMARY_KEY = "primary_key";
141 private static final String ATTRIBUTE_FIELD_TYPE = "type";
142 private static final String NODE_FORMATTING = "formatting";
143 // private static final String ATTRIBUTE_TEXT_FORMAT_MULTILINE = "format_text_multiline";
144 private static final String ATTRIBUTE_USE_THOUSANDS_SEPARATOR = "format_thousands_separator";
145 private static final String ATTRIBUTE_DECIMAL_PLACES = "format_decimal_places";
146 private static final String NODE_RELATIONSHIPS = "relationships";
147 private static final String NODE_RELATIONSHIP = "relationship";
148 private static final String ATTRIBUTE_RELATIONSHIP_FROM_FIELD = "key";
149 private static final String ATTRIBUTE_RELATIONSHIP_TO_TABLE = "other_table";
150 private static final String ATTRIBUTE_RELATIONSHIP_TO_FIELD = "other_key";
151 private static final String NODE_DATA_LAYOUTS = "data_layouts";
152 private static final String NODE_DATA_LAYOUT = "data_layout";
153 private static final String NODE_DATA_LAYOUT_GROUPS = "data_layout_groups";
154 private static final String NODE_DATA_LAYOUT_GROUP = "data_layout_group";
155 private static final String ATTRIBUTE_LAYOUT_GROUP_COLUMNS_COUNT = "columns_count";
156 private static final String NODE_DATA_LAYOUT_NOTEBOOK = "data_layout_notebook";
157 private static final String NODE_DATA_LAYOUT_PORTAL = "data_layout_portal";
158 private static final String NODE_DATA_LAYOUT_PORTAL_NAVIGATIONRELATIONSHIP = "portal_navigation_relationship";
159 private static final String ATTRIBUTE_PORTAL_NAVIGATION_TYPE = "navigation_type";
160 private static final String ATTRIBUTE_PORTAL_NAVIGATION_TYPE_AUTOMATIC = "automatic";
161 private static final String ATTRIBUTE_PORTAL_NAVIGATION_TYPE_SPECIFIC = "specific";
162 private static final String ATTRIBUTE_PORTAL_NAVIGATION_TYPE_NONE = "none";
163 private static final String ATTRIBUTE_RELATIONSHIP_NAME = "relationship";
164 private static final String ATTRIBUTE_RELATED_RELATIONSHIP_NAME = "related_relationship";
165 private static final String NODE_DATA_LAYOUT_ITEM = "data_layout_item";
166 private static final String NODE_CUSTOM_TITLE = "title_custom";
167 private static final String ATTRIBUTE_CUSTOM_TITLE_USE_CUSTOM = "use_custom";
168 private static final String NODE_DATA_LAYOUT_ITEM_GROUPBY = "data_layout_item_groupby";
169 private static final String NODE_GROUPBY = "groupby";
170 private static final String NODE_SECONDARY_FIELDS = "secondary_fields";
171 private static final String ATTRIBUTE_USE_DEFAULT_FORMATTING = "use_default_formatting";
172 private static final String LAYOUT_NAME_DETAILS = "details";
173 private static final String LAYOUT_NAME_LIST = "list";
174 private static final String QUOTE_FOR_FILE_FORMAT = "\"";
176 public void setFileURI(final String fileURI) {
177 this.fileURI = fileURI;
180 public String getFileURI() {
184 // TODO: Make sure these have the correct values.
185 public enum LoadFailureCodes {
186 LOAD_FAILURE_CODE_NONE, LOAD_FAILURE_CODE_NOT_FOUND, LOAD_FAILURE_CODE_FILE_VERSION_TOO_NEW
189 public boolean load() {
190 final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
191 DocumentBuilder documentBuilder = null;
193 documentBuilder = dbf.newDocumentBuilder();
194 } catch (final ParserConfigurationException e) {
195 // TODO Auto-generated catch block
201 xmlDocument = documentBuilder.parse(fileURI);
202 } catch (final SAXException e) {
203 // TODO Auto-generated catch block
206 } catch (final IOException e) {
207 // TODO Auto-generated catch block
212 final Element rootNode = xmlDocument.getDocumentElement();
213 if (rootNode.getNodeName() != NODE_ROOT) {
214 Log.error("Unexpected XML root node name found: " + rootNode.getNodeName());
218 //Get the database title, falling back to the deprecated XML format for it:
219 //TODO: load() show complain (via an enum result) if the document format version is less than 7.
220 final String databaseTitleStr = rootNode.getAttribute(ATTRIBUTE_TITLE);
221 final String deprecatedDatabaseTitleStr = rootNode.getAttribute(DEPRECATED_ATTRIBUTE_DATABASE_TITLE);
222 if(!StringUtils.isEmpty(databaseTitleStr)) {
223 databaseTitle.setTitleOriginal(databaseTitleStr);
225 databaseTitle.setTitleOriginal(deprecatedDatabaseTitleStr);
227 loadTitle(rootNode, databaseTitle);
229 translationOriginalLocale = rootNode.getAttribute(ATTRIBUTE_TRANSLATION_ORIGINAL_LOCALE);
230 translationAvailableLocales.add(translationOriginalLocale); // Just a cache.
232 isExample = getAttributeAsBoolean(rootNode, ATTRIBUTE_IS_EXAMPLE);
234 final Element nodeConnection = getElementByName(rootNode, NODE_CONNECTION);
235 if (nodeConnection != null) {
236 final String strHostingMode = nodeConnection.getAttribute(ATTRIBUTE_CONNECTION_HOSTING_MODE);
237 if (strHostingMode == "postgres_central") {
238 hostingMode = HostingMode.HOSTING_MODE_POSTGRES_CENTRAL;
239 } else if (strHostingMode == "sqlite") {
240 hostingMode = HostingMode.HOSTING_MODE_SQLITE;
242 hostingMode = HostingMode.HOSTING_MODE_POSTGRES_SELF;
245 connectionServer = nodeConnection.getAttribute(ATTRIBUTE_CONNECTION_SERVER);
246 connectionDatabase = nodeConnection.getAttribute(ATTRIBUTE_CONNECTION_DATABASE);
247 connectionPort = (int) getAttributeAsDecimal(nodeConnection, ATTRIBUTE_CONNECTION_PORT);
250 // We first load the fields, relationships, etc,
252 final List<Node> listTableNodes = getChildrenByTagName(rootNode, NODE_TABLE);
253 for (final Node node : listTableNodes) {
254 if (!(node instanceof Element)) {
258 final Element element = (Element) node;
259 final TableInfo info = loadTableNodeBasic(element);
260 tablesMap.put(info.getName(), info);
263 // We then load the layouts for all tables, because they
264 // need the fields and relationships for all tables:
265 for (final Node node : listTableNodes) {
266 if (!(node instanceof Element)) {
270 final Element element = (Element) node;
271 final String tableName = element.getAttribute(ATTRIBUTE_NAME);
273 // We first load the fields, relationships, etc:
274 final TableInfo info = getTableInfo(tableName);
279 // We then load the layouts afterwards, because they
280 // need the fields and relationships:
281 loadTableLayouts(element, info);
283 tablesMap.put(info.getName(), info);
289 private Element getElementByName(final Element parentElement, final String tagName) {
290 final List<Node> listNodes = getChildrenByTagName(parentElement, tagName);
291 if (listNodes == null) {
295 if (listNodes.size() == 0) {
299 return (Element) listNodes.get(0);
302 private boolean getAttributeAsBoolean(final Element node, final String attributeName) {
303 final String str = node.getAttribute(attributeName);
308 return str.equals("true");
311 private void setAttributeAsBoolean(final Element node, final String attributeName, final boolean value) {
312 final String str = value ? "true" : "false";
313 node.setAttribute(attributeName, str);
316 private double getAttributeAsDecimal(final Element node, final String attributeName) {
317 final String str = node.getAttribute(attributeName);
318 if (StringUtils.isEmpty(str)) {
324 value = Double.valueOf(str);
325 } catch (final NumberFormatException e) {
326 // e.printStackTrace();
332 private String getStringForDecimal(final double value) {
333 final NumberFormat format = NumberFormat.getInstance(Locale.US);
334 format.setGroupingUsed(false); // TODO: Does this change it system-wide?
335 return format.format(value);
338 private void setAttributeAsDecimal(final Element node, final String attributeName, final double value) {
339 node.setAttribute(attributeName, getStringForDecimal(value));
343 * Load a title and its translations.
346 * The XML Element that may contain a title attribute and a trans_set of translations of the title.
349 private void loadTitle(final Element node, final Translatable title) {
350 title.setName(node.getAttribute(ATTRIBUTE_NAME));
352 title.setTitleOriginal(node.getAttribute(ATTRIBUTE_TITLE));
354 final Element nodeSet = getElementByName(node, NODE_TRANSLATIONS_SET);
355 if (nodeSet == null) {
359 final List<Node> listNodes = getChildrenByTagName(nodeSet, NODE_TRANSLATIONS);
360 if (listNodes == null) {
364 for (final Node transNode : listNodes) {
365 if (!(transNode instanceof Element)) {
369 final Element element = (Element) transNode;
371 final String locale = element.getAttribute(ATTRIBUTE_TRANSLATION_LOCALE);
372 final String translatedTitle = element.getAttribute(ATTRIBUTE_TRANSLATION_TITLE);
373 if (!StringUtils.isEmpty(locale) && !StringUtils.isEmpty(translatedTitle)) {
374 title.setTitle(translatedTitle, locale);
376 // Remember any new translation locales in our cached list:
377 if (!translationAvailableLocales.contains(locale)) {
378 translationAvailableLocales.add(locale);
384 private void saveTitle(final org.w3c.dom.Document doc, final Element node, final Translatable title) {
385 node.setAttribute(ATTRIBUTE_NAME, title.getName());
387 node.setAttribute(ATTRIBUTE_TITLE, title.getTitleOriginal());
389 final Element nodeSet = createElement(doc, node, NODE_TRANSLATIONS_SET);
391 for (final Entry<String, String> entry : title.getTranslationsMap().entrySet()) {
392 final Element element = createElement(doc, nodeSet, NODE_TRANSLATIONS);
394 element.setAttribute(ATTRIBUTE_TRANSLATION_LOCALE, entry.getKey());
395 element.setAttribute(ATTRIBUTE_TRANSLATION_TITLE, entry.getValue());
403 private TableInfo loadTableNodeBasic(final Element tableNode) {
404 final TableInfo info = new TableInfo();
405 loadTitle(tableNode, info);
406 final String tableName = info.getName();
408 info.isDefault = getAttributeAsBoolean(tableNode, ATTRIBUTE_DEFAULT);
409 info.isHidden = getAttributeAsBoolean(tableNode, ATTRIBUTE_HIDDEN);
411 // These should be loaded before the fields, because the fields use them.
412 final Element relationshipsNode = getElementByName(tableNode, NODE_RELATIONSHIPS);
413 if (relationshipsNode != null) {
414 final List<Node> listNodes = getChildrenByTagName(relationshipsNode, NODE_RELATIONSHIP);
415 for (final Node node : listNodes) {
416 if (!(node instanceof Element)) {
420 final Element element = (Element) node;
421 final Relationship relationship = new Relationship();
422 loadTitle(element, relationship);
423 relationship.setFromTable(tableName);
424 relationship.setFromField(element.getAttribute(ATTRIBUTE_RELATIONSHIP_FROM_FIELD));
425 relationship.setToTable(element.getAttribute(ATTRIBUTE_RELATIONSHIP_TO_TABLE));
426 relationship.setToField(element.getAttribute(ATTRIBUTE_RELATIONSHIP_TO_FIELD));
428 info.relationshipsMap.put(relationship.getName(), relationship);
432 final Element fieldsNode = getElementByName(tableNode, NODE_FIELDS);
433 if (fieldsNode != null) {
434 final List<Node> listNodes = getChildrenByTagName(fieldsNode, NODE_FIELD);
435 for (final Node node : listNodes) {
436 if (!(node instanceof Element)) {
440 final Element element = (Element) node;
441 final Field field = new Field();
442 loadField(element, field);
444 info.fieldsMap.put(field.getName(), field);
448 // We do not normally use this,
449 // though we do use it during testing, in SelfHoster, to recreate the database data.
450 final Element exampleRowsNode = getElementByName(tableNode, NODE_EXAMPLE_ROWS);
451 if (exampleRowsNode != null) {
453 final List<Map<String, DataItem>> exampleRows = new ArrayList<Map<String, DataItem>>();
454 final List<Node> listNodes = getChildrenByTagName(exampleRowsNode, NODE_EXAMPLE_ROW);
455 for (final Node node : listNodes) {
456 if (!(node instanceof Element)) {
460 final Element element = (Element) node;
461 final Map<String, DataItem> row = new HashMap<String, DataItem>();
463 final List<Node> listNodesValues = getChildrenByTagName(element, NODE_VALUE);
464 for (final Node nodeValue : listNodesValues) {
465 if (!(nodeValue instanceof Element)) {
469 final Element elementValue = (Element) nodeValue;
470 final String fieldName = elementValue.getAttribute(ATTRIBUTE_COLUMN);
471 if (StringUtils.isEmpty(fieldName)) {
475 DataItem value = null;
476 final Field field = info.fieldsMap.get(fieldName);
478 value = getNodeTextChildAsValue(elementValue, field.getGlomType());
480 row.put(fieldName, value);
483 exampleRows.add(row);
486 info.exampleRows = exampleRows;
493 * @param elementValue
497 private DataItem getNodeTextChildAsValue(final Element element, final GlomFieldType type) {
498 final DataItem result = new DataItem();
500 final String str = element.getTextContent();
502 // Unescape "" to ", because to_file_format() escaped ", as specified by the CSV RFC:
503 String unescaped = "";
504 if (type == GlomFieldType.TYPE_IMAGE) {
505 unescaped = str; // binary data does not have quote characters so we do not bother to escape or unescape it.
507 unescaped = str.replace(QUOTE_FOR_FILE_FORMAT + QUOTE_FOR_FILE_FORMAT, QUOTE_FOR_FILE_FORMAT);
512 final boolean value = (unescaped == "true");
513 result.setBoolean(value);
517 final DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, Locale.ROOT);
520 value = dateFormat.parse(unescaped);
521 } catch (final ParseException e) {
522 // e.printStackTrace();
524 result.setDate(value);
528 //Glom (at least since 2.23/24) uses base64 for the images:
530 //Discover the mime type:
531 final byte[] bytes = com.google.gwt.user.server.Base64Utils.fromBase64(unescaped);
533 final InputStream is = new ByteArrayInputStream(bytes);
534 String contentType = "";
536 contentType = URLConnection.guessContentTypeFromStream(is);
537 } catch (IOException e) {
538 Log.error("getNodeTextChildAsValue(): unrecognised image data content type.");
541 final String base64 = "data:" + contentType + ";base64," + unescaped;
542 result.setImageDataUrl(base64);
544 result.setImageData(bytes);
550 value = Double.valueOf(unescaped);
551 } catch (final NumberFormatException e) {
552 // e.printStackTrace();
555 result.setNumber(value);
559 result.setText(unescaped);
565 Log.error("getNodeTextChildAsValue(): unexpected or invalid field type.");
572 private void setNodeTextChildAsValue(final Element element, final DataItem value, final GlomFieldType type) {
577 str = value.getBoolean() ? "true" : "false";
581 // TODO: This is not really the format used by the Glom document:
582 final DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, Locale.ROOT);
583 str = dateFormat.format(value.getDate());
591 str = getStringForDecimal(value.getNumber());
595 str = value.getText();
601 Log.error("setNodeTextChildAsValue(): unexpected or invalid field type.");
605 final String escaped = str.replace(QUOTE_FOR_FILE_FORMAT, QUOTE_FOR_FILE_FORMAT + QUOTE_FOR_FILE_FORMAT);
606 element.setTextContent(escaped);
609 private void saveTableNodeBasic(final org.w3c.dom.Document doc, final Element tableNode, final TableInfo info) {
610 saveTitle(doc, tableNode, info);
612 setAttributeAsBoolean(tableNode, ATTRIBUTE_DEFAULT, info.isDefault);
613 setAttributeAsBoolean(tableNode, ATTRIBUTE_HIDDEN, info.isHidden);
615 final Element relationshipsNode = createElement(doc, tableNode, NODE_RELATIONSHIPS);
616 for (final Relationship relationship : info.relationshipsMap.values()) {
617 final Element element = createElement(doc, relationshipsNode, NODE_RELATIONSHIP);
618 saveTitle(doc, element, relationship);
620 element.setAttribute(ATTRIBUTE_RELATIONSHIP_FROM_FIELD, relationship.getFromField());
621 element.setAttribute(ATTRIBUTE_RELATIONSHIP_TO_TABLE, relationship.getToTable());
622 element.setAttribute(ATTRIBUTE_RELATIONSHIP_TO_FIELD, relationship.getToField());
625 final Element fieldsNode = createElement(doc, tableNode, NODE_FIELDS);
626 for (final Field field : info.fieldsMap.values()) {
627 final Element element = createElement(doc, fieldsNode, NODE_FIELD);
628 saveField(doc, element, field);
631 final Element exampleRowsNode = createElement(doc, tableNode, NODE_EXAMPLE_ROWS);
633 for (final Map<String, DataItem> row : info.exampleRows) {
634 final Element node = createElement(doc, exampleRowsNode, NODE_EXAMPLE_ROW);
636 // TODO: This assumes that fieldsMap.values() will have the same sequence as the values,
638 for (final Field field : info.fieldsMap.values()) {
639 if (i < row.size()) {
643 final String fieldName = field.getName();
644 if (StringUtils.isEmpty(fieldName)) {
648 final DataItem dataItem = row.get(fieldName);
649 if (dataItem == null) {
653 final Element elementValue = createElement(doc, node, NODE_VALUE);
654 elementValue.setAttribute(ATTRIBUTE_COLUMN, fieldName);
655 setNodeTextChildAsValue(elementValue, dataItem, field.getGlomType());
665 private void saveField(final org.w3c.dom.Document doc, final Element element, final Field field) {
666 saveTitle(doc, element, field);
668 String fieldTypeStr = "";
670 switch (field.getGlomType()) {
672 fieldTypeStr = "Boolean";
675 fieldTypeStr = "Date";
678 fieldTypeStr = "Image";
681 fieldTypeStr = "Number";
684 fieldTypeStr = "Text";
687 fieldTypeStr = "Time";
692 element.setAttribute(ATTRIBUTE_FIELD_TYPE, fieldTypeStr);
694 setAttributeAsBoolean(element, ATTRIBUTE_PRIMARY_KEY, field.getPrimaryKey());
696 final Element elementFormatting = createElement(doc, element, NODE_FORMATTING);
697 saveFormatting(elementFormatting, field.getFormatting());
701 * @param elementFormatting
704 private void saveFormatting(final Element element, final Formatting formatting) {
705 // formatting.setTextFormatMultiline(getAttributeAsBoolean(elementFormatting, ATTRIBUTE_TEXT_FORMAT_MULTILINE));
707 final NumericFormat numericFormatting = formatting.getNumericFormat();
708 if (numericFormatting != null) {
709 setAttributeAsBoolean(element, ATTRIBUTE_USE_THOUSANDS_SEPARATOR,
710 numericFormatting.getUseThousandsSeparator());
711 setAttributeAsDecimal(element, ATTRIBUTE_DECIMAL_PLACES, numericFormatting.getDecimalPlaces());
719 private void loadTableLayouts(final Element tableNode, final TableInfo info) {
720 final String tableName = info.getName();
722 final Element layoutsNode = getElementByName(tableNode, NODE_DATA_LAYOUTS);
723 if (layoutsNode != null) {
724 final List<Node> listNodes = getChildrenByTagName(layoutsNode, NODE_DATA_LAYOUT);
725 for (final Node node : listNodes) {
726 if (!(node instanceof Element)) {
730 final Element element = (Element) node;
731 final String name = element.getAttribute(ATTRIBUTE_NAME);
732 final List<LayoutGroup> listLayoutGroups = loadLayoutNode(element, tableName);
733 if (name.equals(LAYOUT_NAME_DETAILS)) {
734 info.layoutGroupsDetails = listLayoutGroups;
735 } else if (name.equals(LAYOUT_NAME_LIST)) {
736 info.layoutGroupsList = listLayoutGroups;
738 Log.error("loadTableNode(): unexpected layout name: " + name);
743 final Element reportsNode = getElementByName(tableNode, NODE_REPORTS);
744 if (reportsNode != null) {
745 final List<Node> listNodes = getChildrenByTagName(reportsNode, NODE_REPORT);
746 for (final Node node : listNodes) {
747 if (!(node instanceof Element)) {
751 final Element element = (Element) node;
752 final Report report = new Report();
753 loadReport(element, report, tableName);
755 info.reportsMap.put(report.getName(), report);
764 private List<LayoutGroup> loadLayoutNode(final Element node, final String tableName) {
769 final List<LayoutGroup> result = new ArrayList<LayoutGroup>();
771 final List<Node> listNodes = getChildrenByTagName(node, NODE_DATA_LAYOUT_GROUPS);
772 for (final Node nodeGroups : listNodes) {
773 if (!(nodeGroups instanceof Element)) {
777 final Element elementGroups = (Element) nodeGroups;
779 final NodeList list = elementGroups.getChildNodes();
780 final int num = list.getLength();
781 for (int i = 0; i < num; i++) {
782 final Node nodeLayoutGroup = list.item(i);
783 if (nodeLayoutGroup == null) {
787 if (!(nodeLayoutGroup instanceof Element)) {
791 final Element element = (Element) nodeLayoutGroup;
792 final String tagName = element.getTagName();
793 if (tagName == NODE_DATA_LAYOUT_GROUP) {
794 final LayoutGroup group = new LayoutGroup();
795 loadDataLayoutGroup(element, group, tableName);
797 } else if (tagName == NODE_DATA_LAYOUT_NOTEBOOK) {
798 final LayoutItemNotebook group = new LayoutItemNotebook();
799 loadDataLayoutGroup(element, group, tableName);
801 } else if (tagName == NODE_DATA_LAYOUT_PORTAL) {
802 final LayoutItemPortal portal = new LayoutItemPortal();
803 loadDataLayoutPortal(element, portal, tableName);
817 private void loadUsesRelationship(final Element element, final String tableName, final UsesRelationship item) {
818 if (element == null) {
826 final String relationshipName = element.getAttribute(ATTRIBUTE_RELATIONSHIP_NAME);
827 Relationship relationship = null;
828 if (!StringUtils.isEmpty(relationshipName)) {
829 // std::cout << " debug in : tableName=" << tableName << ", relationshipName=" << relationship_name <<
831 relationship = getRelationship(tableName, relationshipName);
832 item.setRelationship(relationship);
834 if (relationship == null) {
835 Log.error("relationship not found: " + relationshipName + ", in table: " + tableName);
839 // TODO: Unit test loading of doubly-related fields.
840 final String relatedRelationshipName = element.getAttribute(ATTRIBUTE_RELATED_RELATIONSHIP_NAME);
841 if (!StringUtils.isEmpty(relatedRelationshipName) && (relationship != null)) {
842 final Relationship relatedRelationship = getRelationship(relationship.getToTable(), relatedRelationshipName);
843 item.setRelatedRelationship(relatedRelationship);
845 if (relatedRelationship == null) {
846 Log.error("related relationship not found in table=" + relationship.getToTable() + ", name="
847 + relatedRelationshipName);
853 * getElementsByTagName() is recursive, but we do not want that.
859 private List<Node> getChildrenByTagName(final Element parentNode, final String tagName) {
860 final List<Node> result = new ArrayList<Node>();
862 final NodeList list = parentNode.getElementsByTagName(tagName);
863 final int num = list.getLength();
864 for (int i = 0; i < num; i++) {
865 final Node node = list.item(i);
870 final Node itemParentNode = node.getParentNode();
871 if (itemParentNode.equals(parentNode)) {
883 private void loadDataLayoutGroup(final Element nodeGroup, final LayoutGroup group, final String tableName) {
884 loadTitle(nodeGroup, group);
886 // Read the column count:
887 int columnCount = (int) getAttributeAsDecimal(nodeGroup, ATTRIBUTE_LAYOUT_GROUP_COLUMNS_COUNT);
888 if (columnCount < 1) {
889 columnCount = 1; // 0 is a useless default.
891 group.setColumnCount(columnCount);
893 // Get the child items:
894 final NodeList listNodes = nodeGroup.getChildNodes();
895 final int num = listNodes.getLength();
896 for (int i = 0; i < num; i++) {
897 final Node node = listNodes.item(i);
898 if (!(node instanceof Element)) {
902 final Element element = (Element) node;
903 final String tagName = element.getTagName();
904 if (tagName == NODE_DATA_LAYOUT_GROUP) {
905 final LayoutGroup childGroup = new LayoutGroup();
906 loadDataLayoutGroup(element, childGroup, tableName);
907 group.addItem(childGroup);
908 } else if (tagName == NODE_DATA_LAYOUT_NOTEBOOK) {
909 final LayoutItemNotebook childGroup = new LayoutItemNotebook();
910 loadDataLayoutGroup(element, childGroup, tableName);
911 group.addItem(childGroup);
912 } else if (tagName == NODE_DATA_LAYOUT_PORTAL) {
913 final LayoutItemPortal childGroup = new LayoutItemPortal();
914 loadDataLayoutPortal(element, childGroup, tableName);
915 group.addItem(childGroup);
916 } else if (element.getTagName() == NODE_DATA_LAYOUT_ITEM) {
917 final LayoutItemField item = new LayoutItemField();
918 loadDataLayoutItemField(element, item, tableName);
920 } else if (element.getTagName() == NODE_DATA_LAYOUT_ITEM_GROUPBY) {
921 final LayoutItemGroupBy item = new LayoutItemGroupBy();
922 loadDataLayoutItemGroupBy(element, item, tableName);
933 private void loadDataLayoutItemGroupBy(final Element element, final LayoutItemGroupBy item, final String tableName) {
934 loadDataLayoutGroup(element, item, tableName);
936 final Element elementGroupBy = getElementByName(element, NODE_GROUPBY);
937 if (elementGroupBy == null) {
941 final LayoutItemField fieldGroupBy = new LayoutItemField();
942 loadDataLayoutItemField(elementGroupBy, fieldGroupBy, tableName);
943 item.setFieldGroupBy(fieldGroupBy);
945 final Element elementSecondaryFields = getElementByName(element, NODE_SECONDARY_FIELDS);
946 if (elementSecondaryFields == null) {
950 final Element elementLayoutGroup = getElementByName(elementSecondaryFields, NODE_DATA_LAYOUT_GROUP);
951 if (elementLayoutGroup != null) {
952 final LayoutGroup secondaryLayoutGroup = new LayoutGroup();
953 loadDataLayoutGroup(elementLayoutGroup, secondaryLayoutGroup, tableName);
954 item.setSecondaryFields(secondaryLayoutGroup);
962 private void loadDataLayoutItemField(final Element element, final LayoutItemField item, final String tableName) {
963 item.setName(element.getAttribute(ATTRIBUTE_NAME));
964 loadUsesRelationship(element, tableName, item);
966 final Element elementCustomTitle = getElementByName(element, NODE_CUSTOM_TITLE);
967 if (elementCustomTitle != null) {
968 final CustomTitle customTitle = item.getCustomTitle();
969 customTitle.setUseCustomTitle(getAttributeAsBoolean(elementCustomTitle, ATTRIBUTE_CUSTOM_TITLE_USE_CUSTOM));
970 loadTitle(elementCustomTitle, customTitle); // LayoutItemField doesn't use its own title member.
973 // Get the actual field:
974 final String fieldName = item.getName();
975 final String inTableName = item.getTableUsed(tableName);
976 final Field field = getField(inTableName, fieldName);
977 item.setFullFieldDetails(field);
979 item.setUseDefaultFormatting(getAttributeAsBoolean(element, ATTRIBUTE_USE_DEFAULT_FORMATTING));
981 final Element elementFormatting = getElementByName(element, NODE_FORMATTING);
982 if (elementFormatting != null) {
983 loadFormatting(elementFormatting, item.getFormatting());
991 private void loadDataLayoutPortal(final Element element, final LayoutItemPortal portal, final String tableName) {
992 loadUsesRelationship(element, tableName, portal);
993 final String relatedTableName = portal.getTableUsed(tableName);
994 loadDataLayoutGroup(element, portal, relatedTableName);
996 final Element elementNavigation = getElementByName(element, NODE_DATA_LAYOUT_PORTAL_NAVIGATIONRELATIONSHIP);
997 if (elementNavigation != null) {
998 final String navigationTypeAsString = elementNavigation.getAttribute(ATTRIBUTE_PORTAL_NAVIGATION_TYPE);
999 if (StringUtils.isEmpty(navigationTypeAsString)
1000 || navigationTypeAsString == ATTRIBUTE_PORTAL_NAVIGATION_TYPE_AUTOMATIC) {
1001 portal.setNavigationType(LayoutItemPortal.NavigationType.NAVIGATION_AUTOMATIC);
1002 } else if (navigationTypeAsString == ATTRIBUTE_PORTAL_NAVIGATION_TYPE_NONE) {
1003 portal.setNavigationType(LayoutItemPortal.NavigationType.NAVIGATION_NONE);
1004 } else if (navigationTypeAsString == ATTRIBUTE_PORTAL_NAVIGATION_TYPE_SPECIFIC) {
1005 // Read the specified relationship name:
1006 final UsesRelationship relationshipNavigationSpecific = new UsesRelationshipImpl();
1007 loadUsesRelationship(elementNavigation, relatedTableName, relationshipNavigationSpecific);
1008 portal.setNavigationRelationshipSpecific(relationshipNavigationSpecific);
1018 private void loadField(final Element element, final Field field) {
1019 loadTitle(element, field);
1021 Field.GlomFieldType fieldType = Field.GlomFieldType.TYPE_INVALID;
1022 final String fieldTypeStr = element.getAttribute(ATTRIBUTE_FIELD_TYPE);
1023 if (!StringUtils.isEmpty(fieldTypeStr)) {
1024 if (fieldTypeStr.equals("Boolean")) {
1025 fieldType = Field.GlomFieldType.TYPE_BOOLEAN;
1026 } else if (fieldTypeStr.equals("Date")) {
1027 fieldType = Field.GlomFieldType.TYPE_DATE;
1028 } else if (fieldTypeStr.equals("Image")) {
1029 fieldType = Field.GlomFieldType.TYPE_IMAGE;
1030 } else if (fieldTypeStr.equals("Number")) {
1031 fieldType = Field.GlomFieldType.TYPE_NUMERIC;
1032 } else if (fieldTypeStr.equals("Text")) {
1033 fieldType = Field.GlomFieldType.TYPE_TEXT;
1034 } else if (fieldTypeStr.equals("Time")) {
1035 fieldType = Field.GlomFieldType.TYPE_TIME;
1039 field.setGlomFieldType(fieldType);
1041 field.setPrimaryKey(getAttributeAsBoolean(element, ATTRIBUTE_PRIMARY_KEY));
1043 final Element elementFormatting = getElementByName(element, NODE_FORMATTING);
1044 if (elementFormatting != null) {
1045 loadFormatting(elementFormatting, field.getFormatting());
1050 * @param elementFormatting
1053 private void loadFormatting(final Element elementFormatting, final Formatting formatting) {
1054 if (elementFormatting == null) {
1058 if (formatting == null) {
1062 // formatting.setTextFormatMultiline(getAttributeAsBoolean(elementFormatting, ATTRIBUTE_TEXT_FORMAT_MULTILINE));
1064 final NumericFormat numericFormatting = formatting.getNumericFormat();
1065 if (numericFormatting != null) {
1066 numericFormatting.setUseThousandsSeparator(getAttributeAsBoolean(elementFormatting,
1067 ATTRIBUTE_USE_THOUSANDS_SEPARATOR));
1069 .setDecimalPlaces((int) getAttributeAsDecimal(elementFormatting, ATTRIBUTE_DECIMAL_PLACES));
1078 private void loadReport(final Element element, final Report report, final String tableName) {
1079 report.setName(element.getAttribute(ATTRIBUTE_NAME));
1080 loadTitle(element, report);
1082 final List<LayoutGroup> listLayoutGroups = loadLayoutNode(element, tableName);
1084 // A report can actually only have one LayoutGroup,
1085 // though it uses the same XML structure as List and Details layouts,
1086 // which (wrongly) suggests that it can have more than one group.
1087 LayoutGroup layoutGroup = null;
1088 if (!listLayoutGroups.isEmpty()) {
1089 layoutGroup = listLayoutGroups.get(0);
1092 report.setLayoutGroup(layoutGroup);
1095 private TableInfo getTableInfo(final String tableName) {
1096 return tablesMap.get(tableName);
1099 public enum HostingMode {
1100 HOSTING_MODE_POSTGRES_CENTRAL, HOSTING_MODE_POSTGRES_SELF, HOSTING_MODE_SQLITE
1103 public String getDatabaseTitle(final String locale) {
1104 return databaseTitle.getTitle(locale);
1107 public String getDatabaseTitleOriginal() {
1108 return databaseTitle.getTitleOriginal();
1111 public List<String> getTranslationAvailableLocales() {
1112 return translationAvailableLocales;
1115 public Document.HostingMode getHostingMode() {
1120 * @param hostingMode
1122 public void setHostingMode(final HostingMode hostingMode) {
1123 this.hostingMode = hostingMode;
1126 public String getConnectionServer() {
1127 return connectionServer;
1130 public int getConnectionPort() {
1131 return connectionPort;
1134 public void setConnectionPort(final int port) {
1135 connectionPort = port;
1138 public String getConnectionDatabase() {
1139 return connectionDatabase;
1142 public List<String> getTableNames() {
1143 // TODO: Return a Set?
1144 return new ArrayList<String>(tablesMap.keySet());
1147 public boolean getTableIsHidden(final String tableName) {
1148 final TableInfo info = getTableInfo(tableName);
1153 return info.isHidden;
1156 public String getTableTitle(final String tableName, final String locale) {
1157 final TableInfo info = getTableInfo(tableName);
1162 return info.getTitle(locale);
1165 public List<Map<String, DataItem>> getExampleRows(final String tableName) {
1166 final TableInfo info = getTableInfo(tableName);
1171 return info.exampleRows;
1174 public String getDefaultTable() {
1175 for (final TableInfo info : tablesMap.values()) {
1176 if (info.isDefault) {
1177 return info.getName();
1184 public boolean getTableIsKnown(final String tableName) {
1185 final TableInfo info = getTableInfo(tableName);
1193 public List<Field> getTableFields(final String tableName) {
1194 final TableInfo info = getTableInfo(tableName);
1199 return new ArrayList<Field>(info.fieldsMap.values());
1202 public Field getField(final String tableName, final String strFieldName) {
1203 final TableInfo info = getTableInfo(tableName);
1208 return info.fieldsMap.get(strFieldName);
1211 public List<LayoutGroup> getDataLayoutGroups(final String layoutName, final String parentTableName) {
1212 final TableInfo info = getTableInfo(parentTableName);
1214 return new ArrayList<LayoutGroup>();
1217 if (layoutName == LAYOUT_NAME_DETAILS) {
1218 return info.layoutGroupsDetails;
1219 } else if (layoutName == LAYOUT_NAME_LIST) {
1220 return info.layoutGroupsList;
1222 return new ArrayList<LayoutGroup>();
1226 public List<String> getReportNames(final String tableName) {
1227 final TableInfo info = getTableInfo(tableName);
1229 return new ArrayList<String>();
1232 return new ArrayList<String>(info.reportsMap.keySet());
1235 public Report getReport(final String tableName, final String reportName) {
1236 final TableInfo info = getTableInfo(tableName);
1241 return info.reportsMap.get(reportName);
1249 public Relationship getFieldUsedInRelationshipToOne(final String tableName, final LayoutItemField layoutField) {
1251 if (layoutField == null) {
1252 Log.error("layoutField was null");
1256 Relationship result = null;
1258 final String tableUsed = layoutField.getTableUsed(tableName);
1259 final TableInfo info = getTableInfo(tableUsed);
1261 // This table is special. We would not create a relationship to it using a field:
1262 // if(tableUsed == GLOM_STANDARD_TABLE_PREFS_TABLE_NAME)
1265 Log.error("table not found: " + tableUsed);
1269 // Look at each relationship:
1270 final String fieldName = layoutField.getName();
1271 for (final Relationship relationship : info.relationshipsMap.values()) {
1272 if (relationship != null) {
1273 // If the relationship uses the field
1274 if (StringUtils.equals(relationship.getFromField(), fieldName)) {
1275 // if the to_table is not hidden:
1276 if (!getTableIsHidden(relationship.getToTable())) {
1277 // TODO_Performance: The use of this convenience method means we get the full relationship
1278 // information again:
1279 if (getRelationshipIsToOne(tableName, relationship.getName())) {
1280 result = relationship;
1292 * @param relationshipName
1295 private boolean getRelationshipIsToOne(final String tableName, final String relationshipName) {
1296 final Relationship relationship = getRelationship(tableName, relationshipName);
1297 if (relationship != null) {
1298 final Field fieldTo = getField(relationship.getToTable(), relationship.getToField());
1299 if (fieldTo != null) {
1300 return (fieldTo.getPrimaryKey() || fieldTo.getUniqueKey());
1309 * @param relationshipName
1312 private Relationship getRelationship(final String tableName, final String relationshipName) {
1313 final TableInfo info = getTableInfo(tableName);
1315 Log.error("table not found: " + tableName);
1319 return info.relationshipsMap.get(relationshipName);
1325 * @param relationship
1329 public TableToViewDetails getPortalSuitableTableToViewDetails(final LayoutItemPortal portal) {
1330 UsesRelationship navigationRelationship = null;
1332 // Check whether a relationship was specified:
1333 if (portal.getNavigationType() == NavigationType.NAVIGATION_AUTOMATIC) {
1334 navigationRelationship = getPortalNavigationRelationshipAutomatic(portal);
1336 navigationRelationship = portal.getNavigationRelationshipSpecific();
1339 // Get the navigation table name from the chosen relationship:
1340 final String directlyRelatedTableName = portal.getTableUsed("" /* not relevant */);
1342 // The navigation_table_name (and therefore, the table_name output parameter,
1343 // as well) stays empty if the navrel type was set to none.
1344 String navigationTableName = null;
1345 if (navigationRelationship != null) {
1346 navigationTableName = navigationRelationship.getTableUsed(directlyRelatedTableName);
1347 } else if (portal.getNavigationType() != NavigationType.NAVIGATION_NONE) {
1348 // An empty result from get_portal_navigation_relationship_automatic() or
1349 // get_navigation_relationship_specific() means we should use the directly related table:
1350 navigationTableName = directlyRelatedTableName;
1353 if (StringUtils.isEmpty(navigationTableName)) {
1358 Log.error("document is null.");
1362 if (getTableIsHidden(navigationTableName)) {
1363 Log.error("navigation_table_name indicates a hidden table: " + navigationTableName);
1367 final TableToViewDetails result = new TableToViewDetails();
1368 result.tableName = navigationTableName;
1369 result.usesRelationship = navigationRelationship;
1378 private UsesRelationship getPortalNavigationRelationshipAutomatic(final LayoutItemPortal portal) {
1383 // If the related table is not hidden then we can just navigate to that:
1384 final String direct_related_table_name = portal.getTableUsed("" /* parent table - not relevant */);
1385 if (!getTableIsHidden(direct_related_table_name)) {
1386 // Non-hidden tables can just be shown directly. Navigate to it:
1389 // If the related table is hidden,
1390 // then find a suitable related non-hidden table by finding the first layout field that mentions one:
1391 final LayoutItemField field = getPortalFieldIsFromNonHiddenRelatedRecord(portal);
1392 if (field != null) {
1393 return field; // Returns the UsesRelationship base part. (A relationship belonging to the portal's
1396 // Instead, find a key field that's used in a relationship,
1397 // and pretend that we are showing the to field as a related field:
1398 final Relationship fieldIndentifies = getPortalFieldIdentifiesNonHiddenRelatedRecord(portal);
1399 if (fieldIndentifies != null) {
1400 final UsesRelationship result = new UsesRelationshipImpl();
1401 result.setRelationship(fieldIndentifies);
1407 // There was no suitable related table to show:
1416 private LayoutItemField getPortalFieldIsFromNonHiddenRelatedRecord(final LayoutItemPortal portal) {
1417 // Find the first field that is from a non-hidden related table.
1423 final LayoutItemField result = null;
1425 final String parent_table_name = portal.getTableUsed("" /* parent table - not relevant */);
1427 final List<LayoutItem> items = portal.getItems();
1428 for (final LayoutItem item : items) {
1429 if (item instanceof LayoutItemField) {
1430 final LayoutItemField field = (LayoutItemField) item;
1431 if (field.getHasRelationshipName()) {
1432 final String table_name = field.getTableUsed(parent_table_name);
1433 if (!(getTableIsHidden(table_name))) {
1444 * @param used_in_relationship
1449 private Relationship getPortalFieldIdentifiesNonHiddenRelatedRecord(final LayoutItemPortal portal) {
1450 // Find the first field that is from a non-hidden related table.
1453 Log.error("document is null");
1457 final String parent_table_name = portal.getTableUsed("" /* parent table - not relevant */);
1459 final List<LayoutItem> items = portal.getItems();
1460 for (final LayoutItem item : items) {
1461 if (item instanceof LayoutItemField) {
1462 final LayoutItemField field = (LayoutItemField) item;
1463 if (field.getHasRelationshipName()) {
1464 final Relationship relationship = getFieldUsedInRelationshipToOne(parent_table_name, field);
1465 if (relationship != null) {
1466 final String table_name = relationship.getToTable();
1467 if (!StringUtils.isEmpty(table_name)) {
1468 if (!(getTableIsHidden(table_name))) {
1469 return relationship;
1483 * @return The destination table name for navigation.
1485 public String getLayoutItemFieldShouldHaveNavigation(final String tableName, final LayoutItemField layoutItem) {
1486 if (StringUtils.isEmpty(tableName)) {
1490 if (layoutItem == null) {
1494 // Check whether the field controls a relationship,
1495 // meaning it identifies a record in another table.
1496 final Relationship fieldUsedInRelationshipToOne = getFieldUsedInRelationshipToOne(tableName, layoutItem);
1497 if (fieldUsedInRelationshipToOne != null) {
1498 return fieldUsedInRelationshipToOne.getToTable();
1501 // Check whether the field identifies a record in another table
1502 // just because it is a primary key in that table:
1503 final Field fieldInfo = layoutItem.getFullFieldDetails();
1504 final boolean fieldIsRelatedPrimaryKey = layoutItem.getHasRelationshipName() && (fieldInfo != null)
1505 && fieldInfo.getPrimaryKey();
1506 if (fieldIsRelatedPrimaryKey) {
1507 return layoutItem.getRelationship().getToTable();
1516 public void setIsExampleFile(final boolean isExample) {
1517 this.isExample = isExample;
1522 public boolean getIsExampleFile() {
1529 public boolean save() {
1530 final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
1531 DocumentBuilder documentBuilder = null;
1533 documentBuilder = dbf.newDocumentBuilder();
1534 } catch (final ParserConfigurationException e) {
1535 // TODO Auto-generated catch block
1536 e.printStackTrace();
1540 final org.w3c.dom.Document doc = documentBuilder.newDocument();
1541 final Element rootNode = doc.createElement(NODE_ROOT);
1542 doc.appendChild(rootNode);
1544 rootNode.setAttribute(ATTRIBUTE_TITLE, databaseTitle.getTitleOriginal());
1545 rootNode.setAttribute(ATTRIBUTE_TRANSLATION_ORIGINAL_LOCALE, translationOriginalLocale);
1546 setAttributeAsBoolean(rootNode, ATTRIBUTE_IS_EXAMPLE, isExample);
1548 String strHostingMode = "";
1549 if (hostingMode == HostingMode.HOSTING_MODE_POSTGRES_CENTRAL) {
1550 strHostingMode = "postgres_central";
1551 } else if (hostingMode == HostingMode.HOSTING_MODE_SQLITE) {
1552 strHostingMode = "sqlite";
1554 strHostingMode = "postgres_self";
1556 final Element nodeConnection = createElement(doc, rootNode, NODE_CONNECTION);
1557 nodeConnection.setAttribute(ATTRIBUTE_CONNECTION_HOSTING_MODE, strHostingMode);
1558 nodeConnection.setAttribute(ATTRIBUTE_CONNECTION_SERVER, connectionServer);
1559 nodeConnection.setAttribute(ATTRIBUTE_CONNECTION_DATABASE, connectionDatabase);
1560 setAttributeAsDecimal(nodeConnection, ATTRIBUTE_CONNECTION_PORT, connectionPort);
1563 for (final TableInfo table : tablesMap.values()) {
1564 final Element nodeTable = createElement(doc, rootNode, NODE_TABLE);
1565 saveTableNodeBasic(doc, nodeTable, table);
1566 saveTableLayouts(doc, nodeTable, table);
1569 final TransformerFactory transformerFactory = TransformerFactory.newInstance();
1570 Transformer transformer;
1572 transformer = transformerFactory.newTransformer();
1573 } catch (final TransformerConfigurationException e) {
1574 // TODO Auto-generated catch block
1575 e.printStackTrace();
1579 // TODO: This probably distorts text nodes,
1580 // so careful when we load/save them. For instance, scripts.
1581 transformer.setOutputProperty(OutputKeys.INDENT, "yes");
1582 transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
1584 // Make sure that the parent directory exists:
1585 final File file = new File(fileURI);
1587 Files.createParentDirs(file);
1588 } catch (final IOException e) {
1589 e.printStackTrace();
1593 final DOMSource source = new DOMSource(doc);
1594 final StreamResult result = new StreamResult(file);
1596 // Output to console for testing
1597 // StreamResult result = new StreamResult(System.out);
1600 transformer.transform(source, result);
1601 } catch (final TransformerException e) {
1602 // TODO Auto-generated catch block
1603 e.printStackTrace();
1615 private void saveTableLayouts(final org.w3c.dom.Document doc, final Element tableNode, final TableInfo table) {
1617 final Element layoutsNode = createElement(doc, tableNode, NODE_DATA_LAYOUTS);
1619 final Element nodeLayoutDetails = createElement(doc, layoutsNode, NODE_DATA_LAYOUT);
1620 nodeLayoutDetails.setAttribute(ATTRIBUTE_NAME, LAYOUT_NAME_DETAILS);
1621 saveLayoutNode(doc, nodeLayoutDetails, table.layoutGroupsDetails);
1623 final Element nodeLayoutList = createElement(doc, layoutsNode, NODE_DATA_LAYOUT);
1624 nodeLayoutList.setAttribute(ATTRIBUTE_NAME, LAYOUT_NAME_LIST);
1625 saveLayoutNode(doc, nodeLayoutList, table.layoutGroupsList);
1627 final Element reportsNode = createElement(doc, tableNode, NODE_REPORTS);
1628 for (final Report report : table.reportsMap.values()) {
1629 final Element element = createElement(doc, reportsNode, NODE_REPORT);
1630 saveReport(doc, element, report);
1640 private void saveReport(final org.w3c.dom.Document doc, final Element element, final Report report) {
1641 // TODO Auto-generated method stub
1645 private void saveLayoutNode(final org.w3c.dom.Document doc, final Element element,
1646 final List<LayoutGroup> layoutGroups) {
1647 final Element elementGroups = createElement(doc, element, NODE_DATA_LAYOUT_GROUPS);
1649 for (final LayoutGroup layoutGroup : layoutGroups) {
1650 if (layoutGroup instanceof LayoutItemNotebook) {
1651 final Element elementGroup = createElement(doc, elementGroups, NODE_DATA_LAYOUT_NOTEBOOK);
1652 saveDataLayoutGroup(doc, elementGroup, layoutGroup);
1653 } else if (layoutGroup instanceof LayoutItemPortal) {
1654 final Element elementGroup = createElement(doc, elementGroups, NODE_DATA_LAYOUT_PORTAL);
1655 saveDataLayoutPortal(doc, elementGroup, (LayoutItemPortal) layoutGroup);
1657 final Element elementGroup = createElement(doc, elementGroups, NODE_DATA_LAYOUT_GROUP);
1658 saveDataLayoutGroup(doc, elementGroup, layoutGroup);
1666 * @param elementGroup
1667 * @param layoutGroup
1669 private void saveDataLayoutPortal(final org.w3c.dom.Document doc, final Element element,
1670 final LayoutItemPortal portal) {
1671 saveUsesRelationship(element, portal);
1672 saveDataLayoutGroup(doc, element, portal);
1674 final Element elementNavigation = createElement(doc, element, NODE_DATA_LAYOUT_PORTAL_NAVIGATIONRELATIONSHIP);
1675 String navigationTypeAsString = "";
1676 switch (portal.getNavigationType()) {
1677 case NAVIGATION_AUTOMATIC:
1678 navigationTypeAsString = ATTRIBUTE_PORTAL_NAVIGATION_TYPE_AUTOMATIC;
1680 case NAVIGATION_NONE:
1681 navigationTypeAsString = ATTRIBUTE_PORTAL_NAVIGATION_TYPE_NONE;
1683 case NAVIGATION_SPECIFIC:
1684 navigationTypeAsString = ATTRIBUTE_PORTAL_NAVIGATION_TYPE_SPECIFIC;
1689 elementNavigation.setAttribute(ATTRIBUTE_PORTAL_NAVIGATION_TYPE, navigationTypeAsString);
1691 if (navigationTypeAsString == ATTRIBUTE_PORTAL_NAVIGATION_TYPE_SPECIFIC) {
1692 // Write the specified relationship name:
1693 saveUsesRelationship(elementNavigation, portal.getNavigationRelationshipSpecific());
1699 * @param elementGroup
1700 * @param layoutGroup
1702 private void saveDataLayoutGroup(final org.w3c.dom.Document doc, final Element nodeGroup, final LayoutGroup group) {
1703 saveTitle(doc, nodeGroup, group);
1705 // Write the column count:
1706 setAttributeAsDecimal(nodeGroup, ATTRIBUTE_LAYOUT_GROUP_COLUMNS_COUNT, group.getColumnCount());
1708 // Write the child items:
1709 for (final LayoutItem layoutItem : group.getItems()) {
1710 if (layoutItem instanceof LayoutItemPortal) {
1711 final Element element = createElement(doc, nodeGroup, NODE_DATA_LAYOUT_PORTAL);
1712 saveDataLayoutPortal(doc, element, (LayoutItemPortal) layoutItem);
1713 } else if (layoutItem instanceof LayoutItemNotebook) {
1714 final Element element = createElement(doc, nodeGroup, NODE_DATA_LAYOUT_NOTEBOOK);
1715 saveDataLayoutGroup(doc, element, (LayoutItemNotebook) layoutItem);
1716 } else if (layoutItem instanceof LayoutGroup) {
1717 final Element element = createElement(doc, nodeGroup, NODE_DATA_LAYOUT_GROUP);
1718 saveDataLayoutGroup(doc, element, (LayoutGroup) layoutItem);
1719 } else if (layoutItem instanceof LayoutItemField) {
1720 final Element element = createElement(doc, nodeGroup, NODE_DATA_LAYOUT_ITEM);
1721 saveDataLayoutItemField(doc, element, (LayoutItemField) layoutItem);
1722 } else if (layoutItem instanceof LayoutItemGroupBy) {
1723 final Element element = createElement(doc, nodeGroup, NODE_DATA_LAYOUT_ITEM_GROUPBY);
1724 saveDataLayoutItemGroupBy(doc, element, (LayoutItemGroupBy) layoutItem);
1734 private void saveDataLayoutItemGroupBy(final org.w3c.dom.Document doc, final Element element,
1735 final LayoutItemGroupBy item) {
1736 saveDataLayoutGroup(doc, element, item);
1738 final Element elementGroupBy = createElement(doc, element, NODE_GROUPBY);
1739 saveDataLayoutItemField(doc, elementGroupBy, item.getFieldGroupBy());
1741 final Element elementSecondaryFields = createElement(doc, element, NODE_SECONDARY_FIELDS);
1742 final Element elementLayoutGroup = createElement(doc, elementSecondaryFields, NODE_DATA_LAYOUT_GROUP);
1743 saveDataLayoutGroup(doc, elementLayoutGroup, item.getSecondaryFields());
1751 private void saveDataLayoutItemField(final org.w3c.dom.Document doc, final Element element,
1752 final LayoutItemField item) {
1753 element.setAttribute(ATTRIBUTE_NAME, item.getName());
1754 saveUsesRelationship(element, item);
1756 final CustomTitle customTitle = item.getCustomTitle();
1757 if (customTitle != null) {
1758 final Element elementCustomTitle = createElement(doc, element, NODE_CUSTOM_TITLE);
1759 setAttributeAsBoolean(elementCustomTitle, ATTRIBUTE_CUSTOM_TITLE_USE_CUSTOM,
1760 customTitle.getUseCustomTitle());
1761 saveTitle(doc, elementCustomTitle, customTitle); // LayoutItemField doesn't use its own title member.
1764 setAttributeAsBoolean(element, ATTRIBUTE_USE_DEFAULT_FORMATTING, item.getUseDefaultFormatting());
1766 final Element elementFormatting = createElement(doc, element, NODE_FORMATTING);
1767 saveFormatting(elementFormatting, item.getFormatting());
1774 private void saveUsesRelationship(final Element element, final UsesRelationship item) {
1775 final Relationship relationship = item.getRelationship();
1776 if (relationship != null) {
1777 element.setAttribute(ATTRIBUTE_RELATIONSHIP_NAME, relationship.getName());
1780 final Relationship relatedRelationship = item.getRelatedRelationship();
1781 if (relatedRelationship != null) {
1782 element.setAttribute(ATTRIBUTE_RELATED_RELATIONSHIP_NAME, relatedRelationship.getName());
1788 * @param nodeConnection
1791 private Element createElement(final org.w3c.dom.Document doc, final Element parentNode, final String name) {
1792 final Element node = doc.createElement(name);
1793 parentNode.appendChild(node);
1797 public String getSelfHostedDirectoryPath() {
1798 final String uriFile = getFileURI();
1799 if (!StringUtils.isEmpty(uriFile)) {
1800 final File file = new File(uriFile);
1801 final File parent = file.getParentFile();
1802 if (parent == null) {
1807 File dataDir = null;
1808 switch (hostingMode) {
1809 case HOSTING_MODE_POSTGRES_SELF:
1810 dataDir = new File(parent, "glom_postgres_data");
1812 case HOSTING_MODE_POSTGRES_CENTRAL:
1815 case HOSTING_MODE_SQLITE:
1823 if (dataDir != null) {
1824 return dataDir.getPath();
1828 // TODO: std::cerr << G_STRFUNC << ": returning empty string." << std::endl;
1834 public void setConnectionDatabase(final String databaseName) {
1835 connectionDatabase = databaseName;
1840 * Gets the primary key Field for the specified table name.
1843 * name of table to search for the primary key field
1844 * @return primary key Field
1846 public Field getTablePrimaryKeyField(final String tableName) {
1847 Field primaryKey = null;
1848 final List<Field> fieldsVec = getTableFields(tableName);
1849 for (int i = 0; i < Utils.safeLongToInt(fieldsVec.size()); i++) {
1850 final Field field = fieldsVec.get(i);
1851 if (field.getPrimaryKey()) {