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.codec.binary.Base64;
51 import org.apache.commons.lang3.StringUtils;
52 import org.glom.web.server.Log;
53 import org.glom.web.server.Utils;
54 import org.glom.web.shared.DataItem;
55 import org.glom.web.shared.libglom.CustomTitle;
56 import org.glom.web.shared.libglom.Field;
57 import org.glom.web.shared.libglom.Field.GlomFieldType;
58 import org.glom.web.shared.libglom.NumericFormat;
59 import org.glom.web.shared.libglom.Relationship;
60 import org.glom.web.shared.libglom.Report;
61 import org.glom.web.shared.libglom.Translatable;
62 import org.glom.web.shared.libglom.layout.Formatting;
63 import org.glom.web.shared.libglom.layout.LayoutGroup;
64 import org.glom.web.shared.libglom.layout.LayoutItem;
65 import org.glom.web.shared.libglom.layout.LayoutItemField;
66 import org.glom.web.shared.libglom.layout.LayoutItemImage;
67 import org.glom.web.shared.libglom.layout.LayoutItemNotebook;
68 import org.glom.web.shared.libglom.layout.LayoutItemPortal;
69 import org.glom.web.shared.libglom.layout.LayoutItemPortal.NavigationType;
70 import org.glom.web.shared.libglom.layout.LayoutItemText;
71 import org.glom.web.shared.libglom.layout.StaticText;
72 import org.glom.web.shared.libglom.layout.TableToViewDetails;
73 import org.glom.web.shared.libglom.layout.UsesRelationship;
74 import org.glom.web.shared.libglom.layout.UsesRelationshipImpl;
75 import org.glom.web.shared.libglom.layout.reportparts.LayoutItemGroupBy;
76 import org.w3c.dom.Element;
77 import org.w3c.dom.Node;
78 import org.w3c.dom.NodeList;
79 import org.xml.sax.SAXException;
81 import com.google.common.io.Files;
84 * @author Murray Cumming <murrayc@openismus.com>
87 public class Document {
89 @SuppressWarnings("serial")
90 private static class TableInfo extends Translatable {
91 private boolean isDefault;
92 private boolean isHidden;
94 private final Hashtable<String, Field> fieldsMap = new Hashtable<String, Field>();
95 private final Hashtable<String, Relationship> relationshipsMap = new Hashtable<String, Relationship>();
96 private final Hashtable<String, Report> reportsMap = new Hashtable<String, Report>();
98 private List<LayoutGroup> layoutGroupsList = new ArrayList<LayoutGroup>();
99 private List<LayoutGroup> layoutGroupsDetails = new ArrayList<LayoutGroup>();
101 // A list of maps (field name to value).
102 private List<Map<String, DataItem>> exampleRows = null;
105 /** This is passed between methods to keep track of the hierarchy of layout items,
106 * so we can later use it to specify the path to a layout item.
108 private static class Path {
109 public String tableName = null;
110 public String layoutName = null;
111 public int[] indices = new int[1];
114 private String fileURI = "";
115 private org.w3c.dom.Document xmlDocument = null;
117 private final Translatable databaseTitle = new Translatable();
118 private String translationOriginalLocale = "";
119 private final List<String> translationAvailableLocales = new ArrayList<String>();
120 private boolean isExample = false;
121 private HostingMode hostingMode = HostingMode.HOSTING_MODE_POSTGRES_CENTRAL;
122 private String connectionServer = "";
123 private String connectionDatabase = "";
124 private int connectionPort = 0;
125 private final Hashtable<String, TableInfo> tablesMap = new Hashtable<String, TableInfo>();
126 private String documentID = null; //Only for use in the Path, for use in image DataItems.
128 private static final String NODE_ROOT = "glom_document";
129 private static final String ATTRIBUTE_IS_EXAMPLE = "is_example";
130 private static final String ATTRIBUTE_TRANSLATION_ORIGINAL_LOCALE = "translation_original_locale";
131 private static final String NODE_CONNECTION = "connection";
132 private static final String ATTRIBUTE_CONNECTION_HOSTING_MODE = "hosting_mode";
133 private static final String ATTRIBUTE_CONNECTION_SERVER = "server";
134 private static final String ATTRIBUTE_CONNECTION_DATABASE = "database";
135 private static final String ATTRIBUTE_CONNECTION_PORT = "port";
136 private static final String NODE_TABLE = "table";
137 private static final String ATTRIBUTE_NAME = "name";
138 private static final String ATTRIBUTE_TITLE = "title";
139 private static final String DEPRECATED_ATTRIBUTE_DATABASE_TITLE = "database_title";
140 private static final String ATTRIBUTE_DEFAULT = "default";
141 private static final String ATTRIBUTE_HIDDEN = "hidden";
142 private static final String NODE_TRANSLATIONS_SET = "trans_set";
143 private static final String NODE_TRANSLATIONS = "trans";
144 private static final String ATTRIBUTE_TRANSLATION_LOCALE = "loc";
145 private static final String ATTRIBUTE_TRANSLATION_TITLE = "val";
146 private static final String NODE_REPORTS = "reports";
147 private static final String NODE_REPORT = "report";
148 private static final String NODE_FIELDS = "fields";
149 private static final String NODE_FIELD = "field";
150 private static final String NODE_EXAMPLE_ROWS = "example_rows";
151 private static final String NODE_EXAMPLE_ROW = "example_row";
152 private static final String NODE_VALUE = "value";
153 private static final String ATTRIBUTE_COLUMN = "column";
154 private static final String ATTRIBUTE_PRIMARY_KEY = "primary_key";
155 private static final String ATTRIBUTE_FIELD_TYPE = "type";
156 private static final String NODE_FORMATTING = "formatting";
157 // private static final String ATTRIBUTE_TEXT_FORMAT_MULTILINE = "format_text_multiline";
158 private static final String ATTRIBUTE_USE_THOUSANDS_SEPARATOR = "format_thousands_separator";
159 private static final String ATTRIBUTE_DECIMAL_PLACES = "format_decimal_places";
160 private static final String NODE_RELATIONSHIPS = "relationships";
161 private static final String NODE_RELATIONSHIP = "relationship";
162 private static final String ATTRIBUTE_RELATIONSHIP_FROM_FIELD = "key";
163 private static final String ATTRIBUTE_RELATIONSHIP_TO_TABLE = "other_table";
164 private static final String ATTRIBUTE_RELATIONSHIP_TO_FIELD = "other_key";
165 private static final String NODE_DATA_LAYOUTS = "data_layouts";
166 private static final String NODE_DATA_LAYOUT = "data_layout";
167 private static final String NODE_DATA_LAYOUT_GROUPS = "data_layout_groups";
168 private static final String NODE_DATA_LAYOUT_GROUP = "data_layout_group";
169 private static final String ATTRIBUTE_LAYOUT_GROUP_COLUMNS_COUNT = "columns_count";
170 private static final String NODE_DATA_LAYOUT_NOTEBOOK = "data_layout_notebook";
171 private static final String NODE_DATA_LAYOUT_PORTAL = "data_layout_portal";
172 private static final String NODE_DATA_LAYOUT_PORTAL_NAVIGATIONRELATIONSHIP = "portal_navigation_relationship";
173 private static final String ATTRIBUTE_PORTAL_NAVIGATION_TYPE = "navigation_type";
174 private static final String ATTRIBUTE_PORTAL_NAVIGATION_TYPE_AUTOMATIC = "automatic";
175 private static final String ATTRIBUTE_PORTAL_NAVIGATION_TYPE_SPECIFIC = "specific";
176 private static final String ATTRIBUTE_PORTAL_NAVIGATION_TYPE_NONE = "none";
177 private static final String ATTRIBUTE_RELATIONSHIP_NAME = "relationship";
178 private static final String ATTRIBUTE_RELATED_RELATIONSHIP_NAME = "related_relationship";
179 private static final String NODE_DATA_LAYOUT_ITEM = "data_layout_item";
180 private static final String NODE_CUSTOM_TITLE = "title_custom";
181 private static final String ATTRIBUTE_CUSTOM_TITLE_USE_CUSTOM = "use_custom";
182 private static final String NODE_DATA_LAYOUT_TEXTOBJECT = "data_layout_text";
183 private static final String NODE_DATA_LAYOUT_TEXTOBJECT_TEXT = "text";
184 private static final String NODE_DATA_LAYOUT_IMAGEOBJECT = "data_layout_image";
185 private static final String NODE_DATA_LAYOUT_ITEM_GROUPBY = "data_layout_item_groupby";
186 private static final String NODE_GROUPBY = "groupby";
187 private static final String NODE_SECONDARY_FIELDS = "secondary_fields";
188 private static final String ATTRIBUTE_USE_DEFAULT_FORMATTING = "use_default_formatting";
189 public static final String LAYOUT_NAME_DETAILS = "details";
190 public static final String LAYOUT_NAME_LIST = "list";
191 private static final String QUOTE_FOR_FILE_FORMAT = "\"";
194 * Instantiate a Document with no documentID,
195 * meaning that its LayoutItemImage items will not be able to provide a URI to request their data.
196 * This constructor is useful for tests.
202 * Instantiate a Document.
204 * @param documentID Used by LayoutItemImage items to provide a URI to request their data.
206 public Document(final String documentID) {
207 this.documentID = documentID;
210 public void setFileURI(final String fileURI) {
211 this.fileURI = fileURI;
214 public String getFileURI() {
218 // TODO: Make sure these have the correct values.
219 public enum LoadFailureCodes {
220 LOAD_FAILURE_CODE_NONE, LOAD_FAILURE_CODE_NOT_FOUND, LOAD_FAILURE_CODE_FILE_VERSION_TOO_NEW
223 public boolean load() {
224 final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
225 DocumentBuilder documentBuilder = null;
227 documentBuilder = dbf.newDocumentBuilder();
228 } catch (final ParserConfigurationException e) {
229 // TODO Auto-generated catch block
235 xmlDocument = documentBuilder.parse(fileURI);
236 } catch (final SAXException e) {
237 // TODO Auto-generated catch block
240 } catch (final IOException e) {
241 // TODO Auto-generated catch block
246 final Element rootNode = xmlDocument.getDocumentElement();
247 if (rootNode.getNodeName() != NODE_ROOT) {
248 Log.error("Unexpected XML root node name found: " + rootNode.getNodeName());
252 //Get the database title, falling back to the deprecated XML format for it:
253 //TODO: load() show complain (via an enum result) if the document format version is less than 7.
254 final String databaseTitleStr = rootNode.getAttribute(ATTRIBUTE_TITLE);
255 final String deprecatedDatabaseTitleStr = rootNode.getAttribute(DEPRECATED_ATTRIBUTE_DATABASE_TITLE);
256 if(!StringUtils.isEmpty(databaseTitleStr)) {
257 databaseTitle.setTitleOriginal(databaseTitleStr);
259 databaseTitle.setTitleOriginal(deprecatedDatabaseTitleStr);
261 loadTitle(rootNode, databaseTitle);
263 translationOriginalLocale = rootNode.getAttribute(ATTRIBUTE_TRANSLATION_ORIGINAL_LOCALE);
264 translationAvailableLocales.add(translationOriginalLocale); // Just a cache.
266 isExample = getAttributeAsBoolean(rootNode, ATTRIBUTE_IS_EXAMPLE);
268 final Element nodeConnection = getElementByName(rootNode, NODE_CONNECTION);
269 if (nodeConnection != null) {
270 final String strHostingMode = nodeConnection.getAttribute(ATTRIBUTE_CONNECTION_HOSTING_MODE);
271 if (strHostingMode.equals("postgres_central")) {
272 hostingMode = HostingMode.HOSTING_MODE_POSTGRES_CENTRAL;
273 } else if (strHostingMode.equals("sqlite")) {
274 hostingMode = HostingMode.HOSTING_MODE_SQLITE;
276 hostingMode = HostingMode.HOSTING_MODE_POSTGRES_SELF;
279 connectionServer = nodeConnection.getAttribute(ATTRIBUTE_CONNECTION_SERVER);
280 connectionDatabase = nodeConnection.getAttribute(ATTRIBUTE_CONNECTION_DATABASE);
281 connectionPort = (int) getAttributeAsDecimal(nodeConnection, ATTRIBUTE_CONNECTION_PORT);
284 // We first load the fields, relationships, etc,
286 final List<Node> listTableNodes = getChildrenByTagName(rootNode, NODE_TABLE);
287 for (final Node node : listTableNodes) {
288 if (!(node instanceof Element)) {
292 final Element element = (Element) node;
293 final TableInfo info = loadTableNodeBasic(element);
294 tablesMap.put(info.getName(), info);
297 // We then load the layouts for all tables, because they
298 // need the fields and relationships for all tables:
299 for (final Node node : listTableNodes) {
300 if (!(node instanceof Element)) {
304 final Element element = (Element) node;
305 final String tableName = element.getAttribute(ATTRIBUTE_NAME);
307 // We first load the fields, relationships, etc:
308 final TableInfo info = getTableInfo(tableName);
313 // We then load the layouts afterwards, because they
314 // need the fields and relationships:
315 loadTableLayouts(element, info);
317 tablesMap.put(info.getName(), info);
323 private Element getElementByName(final Element parentElement, final String tagName) {
324 final List<Node> listNodes = getChildrenByTagName(parentElement, tagName);
325 if (listNodes == null) {
329 if (listNodes.size() == 0) {
333 return (Element) listNodes.get(0);
336 private boolean getAttributeAsBoolean(final Element node, final String attributeName) {
337 final String str = node.getAttribute(attributeName);
342 return str.equals("true");
345 private void setAttributeAsBoolean(final Element node, final String attributeName, final boolean value) {
346 final String str = value ? "true" : "false";
347 node.setAttribute(attributeName, str);
350 private double getAttributeAsDecimal(final Element node, final String attributeName) {
351 final String str = node.getAttribute(attributeName);
352 if (StringUtils.isEmpty(str)) {
358 value = Double.valueOf(str);
359 } catch (final NumberFormatException e) {
360 // e.printStackTrace();
366 private String getStringForDecimal(final double value) {
367 final NumberFormat format = NumberFormat.getInstance(Locale.US);
368 format.setGroupingUsed(false); // TODO: Does this change it system-wide?
369 return format.format(value);
372 private void setAttributeAsDecimal(final Element node, final String attributeName, final double value) {
373 node.setAttribute(attributeName, getStringForDecimal(value));
377 * Load a title and its translations.
380 * The XML Element that may contain a title attribute and a trans_set of translations of the title.
383 private void loadTitle(final Element node, final Translatable title) {
384 title.setName(node.getAttribute(ATTRIBUTE_NAME));
386 title.setTitleOriginal(node.getAttribute(ATTRIBUTE_TITLE));
388 final Element nodeSet = getElementByName(node, NODE_TRANSLATIONS_SET);
389 if (nodeSet == null) {
393 final List<Node> listNodes = getChildrenByTagName(nodeSet, NODE_TRANSLATIONS);
394 if (listNodes == null) {
398 for (final Node transNode : listNodes) {
399 if (!(transNode instanceof Element)) {
403 final Element element = (Element) transNode;
405 final String locale = element.getAttribute(ATTRIBUTE_TRANSLATION_LOCALE);
406 final String translatedTitle = element.getAttribute(ATTRIBUTE_TRANSLATION_TITLE);
407 if (!StringUtils.isEmpty(locale) && !StringUtils.isEmpty(translatedTitle)) {
408 title.setTitle(translatedTitle, locale);
410 // Remember any new translation locales in our cached list:
411 if (!translationAvailableLocales.contains(locale)) {
412 translationAvailableLocales.add(locale);
418 private void saveTitle(final org.w3c.dom.Document doc, final Element node, final Translatable title) {
419 node.setAttribute(ATTRIBUTE_NAME, title.getName());
421 node.setAttribute(ATTRIBUTE_TITLE, title.getTitleOriginal());
423 final Element nodeSet = createElement(doc, node, NODE_TRANSLATIONS_SET);
425 for (final Entry<String, String> entry : title.getTranslationsMap().entrySet()) {
426 final Element element = createElement(doc, nodeSet, NODE_TRANSLATIONS);
428 element.setAttribute(ATTRIBUTE_TRANSLATION_LOCALE, entry.getKey());
429 element.setAttribute(ATTRIBUTE_TRANSLATION_TITLE, entry.getValue());
437 private TableInfo loadTableNodeBasic(final Element tableNode) {
438 final TableInfo info = new TableInfo();
439 loadTitle(tableNode, info);
440 final String tableName = info.getName();
442 info.isDefault = getAttributeAsBoolean(tableNode, ATTRIBUTE_DEFAULT);
443 info.isHidden = getAttributeAsBoolean(tableNode, ATTRIBUTE_HIDDEN);
445 // These should be loaded before the fields, because the fields use them.
446 final Element relationshipsNode = getElementByName(tableNode, NODE_RELATIONSHIPS);
447 if (relationshipsNode != null) {
448 final List<Node> listNodes = getChildrenByTagName(relationshipsNode, NODE_RELATIONSHIP);
449 for (final Node node : listNodes) {
450 if (!(node instanceof Element)) {
454 final Element element = (Element) node;
455 final Relationship relationship = new Relationship();
456 loadTitle(element, relationship);
457 relationship.setFromTable(tableName);
458 relationship.setFromField(element.getAttribute(ATTRIBUTE_RELATIONSHIP_FROM_FIELD));
459 relationship.setToTable(element.getAttribute(ATTRIBUTE_RELATIONSHIP_TO_TABLE));
460 relationship.setToField(element.getAttribute(ATTRIBUTE_RELATIONSHIP_TO_FIELD));
462 info.relationshipsMap.put(relationship.getName(), relationship);
466 final Element fieldsNode = getElementByName(tableNode, NODE_FIELDS);
467 if (fieldsNode != null) {
468 final List<Node> listNodes = getChildrenByTagName(fieldsNode, NODE_FIELD);
469 for (final Node node : listNodes) {
470 if (!(node instanceof Element)) {
474 final Element element = (Element) node;
475 final Field field = new Field();
476 loadField(element, field);
478 info.fieldsMap.put(field.getName(), field);
482 // We do not normally use this,
483 // though we do use it during testing, in SelfHoster, to recreate the database data.
484 final Element exampleRowsNode = getElementByName(tableNode, NODE_EXAMPLE_ROWS);
485 if (exampleRowsNode != null) {
487 final List<Map<String, DataItem>> exampleRows = new ArrayList<Map<String, DataItem>>();
488 final List<Node> listNodes = getChildrenByTagName(exampleRowsNode, NODE_EXAMPLE_ROW);
489 for (final Node node : listNodes) {
490 if (!(node instanceof Element)) {
494 final Element element = (Element) node;
495 final Map<String, DataItem> row = new HashMap<String, DataItem>();
497 final List<Node> listNodesValues = getChildrenByTagName(element, NODE_VALUE);
498 for (final Node nodeValue : listNodesValues) {
499 if (!(nodeValue instanceof Element)) {
503 final Element elementValue = (Element) nodeValue;
504 final String fieldName = elementValue.getAttribute(ATTRIBUTE_COLUMN);
505 if (StringUtils.isEmpty(fieldName)) {
509 DataItem value = null;
510 final Field field = info.fieldsMap.get(fieldName);
512 value = getNodeTextChildAsValue(elementValue, field.getGlomType());
514 row.put(fieldName, value);
517 exampleRows.add(row);
520 info.exampleRows = exampleRows;
527 * @param elementValue
531 private DataItem getNodeTextChildAsValue(final Element element, final GlomFieldType type) {
532 final DataItem result = new DataItem();
534 final String str = element.getTextContent();
536 // Unescape "" to ", because to_file_format() escaped ", as specified by the CSV RFC:
537 String unescaped = "";
538 if (type == GlomFieldType.TYPE_IMAGE) {
539 unescaped = str; // binary data does not have quote characters so we do not bother to escape or unescape it.
541 unescaped = str.replace(QUOTE_FOR_FILE_FORMAT + QUOTE_FOR_FILE_FORMAT, QUOTE_FOR_FILE_FORMAT);
546 final boolean value = (unescaped.equals("true"));
547 result.setBoolean(value);
551 final DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, Locale.ROOT);
554 value = dateFormat.parse(unescaped);
555 } catch (final ParseException e) {
556 // e.printStackTrace();
558 result.setDate(value);
562 //Glom (at least since 2.23/24) uses base64 for the images:
564 //This is only used on the server-side,
565 //either to create a database, during tests,
566 //or to return the full data from our OnlineGlomImage service.
567 //It is removed before being passed to the client-side.
569 /* This does not seem to work with the text from g_base64_encode() that Glom uses,
570 * maybe because of the newlines, which are apparently OK:
571 * http://en.wikipedia.org/wiki/Base64#MIME
572 * final byte[] bytes = com.google.gwt.user.server.Base64Utils.fromBase64(unescaped);
575 /* Use org.apache.commons.codec.binary.Base64: */
576 final Base64 decoder = new Base64();
577 byte[] bytes = (byte[]) decoder.decode(unescaped.getBytes());
579 result.setImageData(bytes);
586 value = Double.valueOf(unescaped);
587 } catch (final NumberFormatException e) {
588 // e.printStackTrace();
591 result.setNumber(value);
595 result.setText(unescaped);
601 Log.error(documentID, "getNodeTextChildAsValue(): unexpected or invalid field type.");
608 private void setNodeTextChildAsValue(final Element element, final DataItem value, final GlomFieldType type) {
613 str = value.getBoolean() ? "true" : "false";
617 // TODO: This is not really the format used by the Glom document:
618 final DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, Locale.ROOT);
619 str = dateFormat.format(value.getDate());
627 str = getStringForDecimal(value.getNumber());
631 str = value.getText();
637 Log.error(documentID, "setNodeTextChildAsValue(): unexpected or invalid field type.");
641 final String escaped = str.replace(QUOTE_FOR_FILE_FORMAT, QUOTE_FOR_FILE_FORMAT + QUOTE_FOR_FILE_FORMAT);
642 element.setTextContent(escaped);
645 private void saveTableNodeBasic(final org.w3c.dom.Document doc, final Element tableNode, final TableInfo info) {
646 saveTitle(doc, tableNode, info);
648 setAttributeAsBoolean(tableNode, ATTRIBUTE_DEFAULT, info.isDefault);
649 setAttributeAsBoolean(tableNode, ATTRIBUTE_HIDDEN, info.isHidden);
651 final Element relationshipsNode = createElement(doc, tableNode, NODE_RELATIONSHIPS);
652 for (final Relationship relationship : info.relationshipsMap.values()) {
653 final Element element = createElement(doc, relationshipsNode, NODE_RELATIONSHIP);
654 saveTitle(doc, element, relationship);
656 element.setAttribute(ATTRIBUTE_RELATIONSHIP_FROM_FIELD, relationship.getFromField());
657 element.setAttribute(ATTRIBUTE_RELATIONSHIP_TO_TABLE, relationship.getToTable());
658 element.setAttribute(ATTRIBUTE_RELATIONSHIP_TO_FIELD, relationship.getToField());
661 final Element fieldsNode = createElement(doc, tableNode, NODE_FIELDS);
662 for (final Field field : info.fieldsMap.values()) {
663 final Element element = createElement(doc, fieldsNode, NODE_FIELD);
664 saveField(doc, element, field);
667 final Element exampleRowsNode = createElement(doc, tableNode, NODE_EXAMPLE_ROWS);
669 for (final Map<String, DataItem> row : info.exampleRows) {
670 final Element node = createElement(doc, exampleRowsNode, NODE_EXAMPLE_ROW);
672 // TODO: This assumes that fieldsMap.values() will have the same sequence as the values,
674 for (final Field field : info.fieldsMap.values()) {
675 if (i < row.size()) {
679 final String fieldName = field.getName();
680 if (StringUtils.isEmpty(fieldName)) {
684 final DataItem dataItem = row.get(fieldName);
685 if (dataItem == null) {
689 final Element elementValue = createElement(doc, node, NODE_VALUE);
690 elementValue.setAttribute(ATTRIBUTE_COLUMN, fieldName);
691 setNodeTextChildAsValue(elementValue, dataItem, field.getGlomType());
701 private void saveField(final org.w3c.dom.Document doc, final Element element, final Field field) {
702 saveTitle(doc, element, field);
704 String fieldTypeStr = "";
706 switch (field.getGlomType()) {
708 fieldTypeStr = "Boolean";
711 fieldTypeStr = "Date";
714 fieldTypeStr = "Image";
717 fieldTypeStr = "Number";
720 fieldTypeStr = "Text";
723 fieldTypeStr = "Time";
728 element.setAttribute(ATTRIBUTE_FIELD_TYPE, fieldTypeStr);
730 setAttributeAsBoolean(element, ATTRIBUTE_PRIMARY_KEY, field.getPrimaryKey());
732 final Element elementFormatting = createElement(doc, element, NODE_FORMATTING);
733 saveFormatting(elementFormatting, field.getFormatting());
737 * @param elementFormatting
740 private void saveFormatting(final Element element, final Formatting formatting) {
741 // formatting.setTextFormatMultiline(getAttributeAsBoolean(elementFormatting, ATTRIBUTE_TEXT_FORMAT_MULTILINE));
743 final NumericFormat numericFormatting = formatting.getNumericFormat();
744 if (numericFormatting != null) {
745 setAttributeAsBoolean(element, ATTRIBUTE_USE_THOUSANDS_SEPARATOR,
746 numericFormatting.getUseThousandsSeparator());
747 setAttributeAsDecimal(element, ATTRIBUTE_DECIMAL_PLACES, numericFormatting.getDecimalPlaces());
755 private void loadTableLayouts(final Element tableNode, final TableInfo info) {
756 final String tableName = info.getName();
758 final Element layoutsNode = getElementByName(tableNode, NODE_DATA_LAYOUTS);
759 if (layoutsNode != null) {
760 final List<Node> listNodes = getChildrenByTagName(layoutsNode, NODE_DATA_LAYOUT);
761 for (final Node node : listNodes) {
762 if (!(node instanceof Element)) {
766 final Element element = (Element) node;
767 final String name = element.getAttribute(ATTRIBUTE_NAME);
768 final List<LayoutGroup> listLayoutGroups = loadLayoutNode(element, tableName, name);
769 if (name.equals(LAYOUT_NAME_DETAILS)) {
770 info.layoutGroupsDetails = listLayoutGroups;
771 } else if (name.equals(LAYOUT_NAME_LIST)) {
772 info.layoutGroupsList = listLayoutGroups;
774 Log.error(documentID, "loadTableNode(): unexpected layout name: " + name);
779 final Element reportsNode = getElementByName(tableNode, NODE_REPORTS);
780 if (reportsNode != null) {
781 final List<Node> listNodes = getChildrenByTagName(reportsNode, NODE_REPORT);
782 for (final Node node : listNodes) {
783 if (!(node instanceof Element)) {
787 final Element element = (Element) node;
788 final Report report = new Report();
789 loadReport(element, report, tableName);
791 info.reportsMap.put(report.getName(), report);
800 private List<LayoutGroup> loadLayoutNode(final Element node, final String tableName, final String layoutName) {
805 final List<LayoutGroup> result = new ArrayList<LayoutGroup>();
807 final List<Node> listNodes = getChildrenByTagName(node, NODE_DATA_LAYOUT_GROUPS);
808 for (final Node nodeGroups : listNodes) {
809 if (!(nodeGroups instanceof Element)) {
813 final Element elementGroups = (Element) nodeGroups;
815 final NodeList list = elementGroups.getChildNodes();
816 final int num = list.getLength();
817 for (int i = 0; i < num; i++) {
818 final Node nodeLayoutGroup = list.item(i);
819 if (nodeLayoutGroup == null) {
823 if (!(nodeLayoutGroup instanceof Element)) {
827 final Path path = new Path();
828 path.tableName = tableName;
829 path.layoutName = layoutName;
830 path.indices[0 /* depth */] = groupIndex;
833 final Element element = (Element) nodeLayoutGroup;
834 final String tagName = element.getTagName();
835 if (tagName.equals(NODE_DATA_LAYOUT_GROUP)) {
836 final LayoutGroup group = new LayoutGroup();
837 loadDataLayoutGroup(element, group, tableName, path);
839 } else if (tagName.equals(NODE_DATA_LAYOUT_NOTEBOOK)) {
840 final LayoutItemNotebook group = new LayoutItemNotebook();
841 loadDataLayoutGroup(element, group, tableName, path);
843 } else if (tagName.equals(NODE_DATA_LAYOUT_PORTAL)) {
844 final LayoutItemPortal portal = new LayoutItemPortal();
845 loadDataLayoutPortal(element, portal, tableName, path);
859 private void loadUsesRelationship(final Element element, final String tableName, final UsesRelationship item) {
860 if (element == null) {
868 final String relationshipName = element.getAttribute(ATTRIBUTE_RELATIONSHIP_NAME);
869 Relationship relationship = null;
870 if (!StringUtils.isEmpty(relationshipName)) {
871 // std::cout << " debug in : tableName=" << tableName << ", relationshipName=" << relationship_name <<
873 relationship = getRelationship(tableName, relationshipName);
874 item.setRelationship(relationship);
876 if (relationship == null) {
877 Log.error("relationship not found: " + relationshipName + ", in table: " + tableName);
881 // TODO: Unit test loading of doubly-related fields.
882 final String relatedRelationshipName = element.getAttribute(ATTRIBUTE_RELATED_RELATIONSHIP_NAME);
883 if (!StringUtils.isEmpty(relatedRelationshipName) && (relationship != null)) {
884 final Relationship relatedRelationship = getRelationship(relationship.getToTable(), relatedRelationshipName);
885 item.setRelatedRelationship(relatedRelationship);
887 if (relatedRelationship == null) {
888 Log.error("related relationship not found in table=" + relationship.getToTable() + ", name="
889 + relatedRelationshipName);
895 * getElementsByTagName() is recursive, but we do not want that.
901 private List<Node> getChildrenByTagName(final Element parentNode, final String tagName) {
902 final List<Node> result = new ArrayList<Node>();
904 final NodeList list = parentNode.getElementsByTagName(tagName);
905 final int num = list.getLength();
906 for (int i = 0; i < num; i++) {
907 final Node node = list.item(i);
912 final Node itemParentNode = node.getParentNode();
913 if (itemParentNode.equals(parentNode)) {
925 private void loadDataLayoutGroup(final Element nodeGroup, final LayoutGroup group, final String tableName, final Path path) {
926 loadTitle(nodeGroup, group);
928 // Read the column count:
929 int columnCount = (int) getAttributeAsDecimal(nodeGroup, ATTRIBUTE_LAYOUT_GROUP_COLUMNS_COUNT);
930 if (columnCount < 1) {
931 columnCount = 1; // 0 is a useless default.
933 group.setColumnCount(columnCount);
935 final int depth = path.indices.length;
937 // Get the child items:
938 final NodeList listNodes = nodeGroup.getChildNodes();
939 final int num = listNodes.getLength();
941 for (int i = 0; i < num; i++) {
943 final Node node = listNodes.item(i);
944 if (!(node instanceof Element)) {
948 final Element element = (Element) node;
949 final String tagName = element.getTagName();
951 //Do not increment pathIndex for an item
952 //that we will not use:
953 if(tagName.equals(NODE_TRANSLATIONS_SET)) {
957 // Create a path of indices for the child:
958 final Path pathChild = new Path();
959 pathChild.tableName = path.tableName;
960 pathChild.layoutName = path.layoutName;
961 pathChild.indices = new int[path.indices.length + 1];
962 System.arraycopy( path.indices, 0, pathChild.indices, 0, path.indices.length );
963 pathChild.indices[depth] = pathIndex;
966 if (tagName.equals(NODE_DATA_LAYOUT_GROUP)) {
967 final LayoutGroup childGroup = new LayoutGroup();
968 loadDataLayoutGroup(element, childGroup, tableName, pathChild);
969 group.addItem(childGroup);
970 } else if (tagName.equals(NODE_DATA_LAYOUT_NOTEBOOK)) {
971 final LayoutItemNotebook childGroup = new LayoutItemNotebook();
972 loadDataLayoutGroup(element, childGroup, tableName, pathChild);
973 group.addItem(childGroup);
974 } else if (tagName.equals(NODE_DATA_LAYOUT_PORTAL)) {
975 final LayoutItemPortal childGroup = new LayoutItemPortal();
976 loadDataLayoutPortal(element, childGroup, tableName, pathChild);
977 group.addItem(childGroup);
978 } else if (tagName.equals(NODE_DATA_LAYOUT_ITEM)) {
979 final LayoutItemField item = new LayoutItemField();
980 loadDataLayoutItemField(element, item, tableName);
982 } else if (tagName.equals(NODE_DATA_LAYOUT_TEXTOBJECT)) {
983 final LayoutItemText item = new LayoutItemText();
984 loadDataLayoutItemText(element, item);
986 } else if (tagName.equals(NODE_DATA_LAYOUT_IMAGEOBJECT)) {
987 final LayoutItemImage item = new LayoutItemImage();
988 loadDataLayoutItemImage(element, item, pathChild);
990 } else if (tagName.equals(NODE_DATA_LAYOUT_ITEM_GROUPBY)) {
991 final LayoutItemGroupBy item = new LayoutItemGroupBy();
992 loadDataLayoutItemGroupBy(element, item, tableName, pathChild);
1002 private void loadDataLayoutItemImage(Element element, LayoutItemImage item, final Path path) {
1003 loadTitle(element, item);
1005 final Element elementValue = getElementByName(element, NODE_VALUE);
1006 if (elementValue == null) {
1010 final DataItem image = getNodeTextChildAsValue(elementValue, Field.GlomFieldType.TYPE_IMAGE);
1012 //This lets the client-side request the full data from our OnlineGlomImage service.
1013 final String layoutPath = Utils.buildImageDataUrl(documentID, path.tableName, path.layoutName, path.indices);
1014 image.setImageDataUrl(layoutPath);
1016 item.setImage(image);
1023 private void loadDataLayoutItemText(Element element, LayoutItemText item) {
1024 loadTitle(element, item);
1026 final Element elementText = getElementByName(element, NODE_DATA_LAYOUT_TEXTOBJECT_TEXT);
1027 if (elementText == null) {
1031 final StaticText text = new StaticText();
1032 loadTitle(elementText, text); //This node reuses the title structure to hold its text.
1041 private void loadDataLayoutItemGroupBy(final Element element, final LayoutItemGroupBy item, final String tableName, final Path path) {
1042 loadDataLayoutGroup(element, item, tableName, path);
1044 final Element elementGroupBy = getElementByName(element, NODE_GROUPBY);
1045 if (elementGroupBy == null) {
1049 final LayoutItemField fieldGroupBy = new LayoutItemField();
1050 loadDataLayoutItemField(elementGroupBy, fieldGroupBy, tableName);
1051 item.setFieldGroupBy(fieldGroupBy);
1053 final Element elementSecondaryFields = getElementByName(element, NODE_SECONDARY_FIELDS);
1054 if (elementSecondaryFields == null) {
1058 final Element elementLayoutGroup = getElementByName(elementSecondaryFields, NODE_DATA_LAYOUT_GROUP);
1059 if (elementLayoutGroup != null) {
1060 final LayoutGroup secondaryLayoutGroup = new LayoutGroup();
1061 loadDataLayoutGroup(elementLayoutGroup, secondaryLayoutGroup, tableName, path); //TODO: Add the main group items count to path first?
1062 item.setSecondaryFields(secondaryLayoutGroup);
1070 private void loadDataLayoutItemField(final Element element, final LayoutItemField item, final String tableName) {
1071 item.setName(element.getAttribute(ATTRIBUTE_NAME));
1072 loadUsesRelationship(element, tableName, item);
1074 final Element elementCustomTitle = getElementByName(element, NODE_CUSTOM_TITLE);
1075 if (elementCustomTitle != null) {
1076 final CustomTitle customTitle = item.getCustomTitle();
1077 customTitle.setUseCustomTitle(getAttributeAsBoolean(elementCustomTitle, ATTRIBUTE_CUSTOM_TITLE_USE_CUSTOM));
1078 loadTitle(elementCustomTitle, customTitle); // LayoutItemField doesn't use its own title member.
1081 // Get the actual field:
1082 final String fieldName = item.getName();
1083 final String inTableName = item.getTableUsed(tableName);
1084 final Field field = getField(inTableName, fieldName);
1085 item.setFullFieldDetails(field);
1087 item.setUseDefaultFormatting(getAttributeAsBoolean(element, ATTRIBUTE_USE_DEFAULT_FORMATTING));
1089 final Element elementFormatting = getElementByName(element, NODE_FORMATTING);
1090 if (elementFormatting != null) {
1091 loadFormatting(elementFormatting, item.getFormatting());
1099 private void loadDataLayoutPortal(final Element element, final LayoutItemPortal portal, final String tableName, final Path path) {
1100 loadUsesRelationship(element, tableName, portal);
1101 final String relatedTableName = portal.getTableUsed(tableName);
1102 loadDataLayoutGroup(element, portal, relatedTableName, path);
1104 final Element elementNavigation = getElementByName(element, NODE_DATA_LAYOUT_PORTAL_NAVIGATIONRELATIONSHIP);
1105 if (elementNavigation != null) {
1106 final String navigationTypeAsString = elementNavigation.getAttribute(ATTRIBUTE_PORTAL_NAVIGATION_TYPE);
1107 if (StringUtils.isEmpty(navigationTypeAsString)
1108 || navigationTypeAsString.equals(ATTRIBUTE_PORTAL_NAVIGATION_TYPE_AUTOMATIC)) {
1109 portal.setNavigationType(LayoutItemPortal.NavigationType.NAVIGATION_AUTOMATIC);
1110 } else if (navigationTypeAsString.equals(ATTRIBUTE_PORTAL_NAVIGATION_TYPE_NONE)) {
1111 portal.setNavigationType(LayoutItemPortal.NavigationType.NAVIGATION_NONE);
1112 } else if (navigationTypeAsString.equals(ATTRIBUTE_PORTAL_NAVIGATION_TYPE_SPECIFIC)) {
1113 // Read the specified relationship name:
1114 final UsesRelationship relationshipNavigationSpecific = new UsesRelationshipImpl();
1115 loadUsesRelationship(elementNavigation, relatedTableName, relationshipNavigationSpecific);
1116 portal.setNavigationRelationshipSpecific(relationshipNavigationSpecific);
1126 private void loadField(final Element element, final Field field) {
1127 loadTitle(element, field);
1129 Field.GlomFieldType fieldType = Field.GlomFieldType.TYPE_INVALID;
1130 final String fieldTypeStr = element.getAttribute(ATTRIBUTE_FIELD_TYPE);
1131 if (!StringUtils.isEmpty(fieldTypeStr)) {
1132 if (fieldTypeStr.equals("Boolean")) {
1133 fieldType = Field.GlomFieldType.TYPE_BOOLEAN;
1134 } else if (fieldTypeStr.equals("Date")) {
1135 fieldType = Field.GlomFieldType.TYPE_DATE;
1136 } else if (fieldTypeStr.equals("Image")) {
1137 fieldType = Field.GlomFieldType.TYPE_IMAGE;
1138 } else if (fieldTypeStr.equals("Number")) {
1139 fieldType = Field.GlomFieldType.TYPE_NUMERIC;
1140 } else if (fieldTypeStr.equals("Text")) {
1141 fieldType = Field.GlomFieldType.TYPE_TEXT;
1142 } else if (fieldTypeStr.equals("Time")) {
1143 fieldType = Field.GlomFieldType.TYPE_TIME;
1147 field.setGlomFieldType(fieldType);
1149 field.setPrimaryKey(getAttributeAsBoolean(element, ATTRIBUTE_PRIMARY_KEY));
1151 final Element elementFormatting = getElementByName(element, NODE_FORMATTING);
1152 if (elementFormatting != null) {
1153 loadFormatting(elementFormatting, field.getFormatting());
1158 * @param elementFormatting
1161 private void loadFormatting(final Element elementFormatting, final Formatting formatting) {
1162 if (elementFormatting == null) {
1166 if (formatting == null) {
1170 // formatting.setTextFormatMultiline(getAttributeAsBoolean(elementFormatting, ATTRIBUTE_TEXT_FORMAT_MULTILINE));
1172 final NumericFormat numericFormatting = formatting.getNumericFormat();
1173 if (numericFormatting != null) {
1174 numericFormatting.setUseThousandsSeparator(getAttributeAsBoolean(elementFormatting,
1175 ATTRIBUTE_USE_THOUSANDS_SEPARATOR));
1177 .setDecimalPlaces((int) getAttributeAsDecimal(elementFormatting, ATTRIBUTE_DECIMAL_PLACES));
1186 private void loadReport(final Element element, final Report report, final String tableName) {
1187 report.setName(element.getAttribute(ATTRIBUTE_NAME));
1188 loadTitle(element, report);
1190 final List<LayoutGroup> listLayoutGroups = loadLayoutNode(element, tableName, null /* not needed */);
1192 // A report can actually only have one LayoutGroup,
1193 // though it uses the same XML structure as List and Details layouts,
1194 // which (wrongly) suggests that it can have more than one group.
1195 LayoutGroup layoutGroup = null;
1196 if (!listLayoutGroups.isEmpty()) {
1197 layoutGroup = listLayoutGroups.get(0);
1200 report.setLayoutGroup(layoutGroup);
1203 private TableInfo getTableInfo(final String tableName) {
1204 return tablesMap.get(tableName);
1207 public enum HostingMode {
1208 HOSTING_MODE_POSTGRES_CENTRAL, HOSTING_MODE_POSTGRES_SELF, HOSTING_MODE_SQLITE
1211 public String getDatabaseTitle(final String locale) {
1212 return databaseTitle.getTitle(locale);
1215 public String getDatabaseTitleOriginal() {
1216 return databaseTitle.getTitleOriginal();
1219 public List<String> getTranslationAvailableLocales() {
1220 return translationAvailableLocales;
1223 public Document.HostingMode getHostingMode() {
1228 * @param hostingMode
1230 public void setHostingMode(final HostingMode hostingMode) {
1231 this.hostingMode = hostingMode;
1234 public String getConnectionServer() {
1235 return connectionServer;
1238 public int getConnectionPort() {
1239 return connectionPort;
1242 public void setConnectionPort(final int port) {
1243 connectionPort = port;
1246 public String getConnectionDatabase() {
1247 return connectionDatabase;
1250 public List<String> getTableNames() {
1251 // TODO: Return a Set?
1252 return new ArrayList<String>(tablesMap.keySet());
1255 public boolean getTableIsHidden(final String tableName) {
1256 final TableInfo info = getTableInfo(tableName);
1261 return info.isHidden;
1264 public String getTableTitle(final String tableName, final String locale) {
1265 final TableInfo info = getTableInfo(tableName);
1270 return info.getTitle(locale);
1273 public List<Map<String, DataItem>> getExampleRows(final String tableName) {
1274 final TableInfo info = getTableInfo(tableName);
1279 return info.exampleRows;
1282 public String getDefaultTable() {
1283 for (final TableInfo info : tablesMap.values()) {
1284 if (info.isDefault) {
1285 return info.getName();
1292 public boolean getTableIsKnown(final String tableName) {
1293 final TableInfo info = getTableInfo(tableName);
1301 public List<Field> getTableFields(final String tableName) {
1302 final TableInfo info = getTableInfo(tableName);
1307 return new ArrayList<Field>(info.fieldsMap.values());
1310 public Field getField(final String tableName, final String strFieldName) {
1311 final TableInfo info = getTableInfo(tableName);
1316 return info.fieldsMap.get(strFieldName);
1319 public List<LayoutGroup> getDataLayoutGroups(final String layoutName, final String parentTableName) {
1320 final TableInfo info = getTableInfo(parentTableName);
1322 return new ArrayList<LayoutGroup>();
1325 if (layoutName.equals(LAYOUT_NAME_DETAILS)) {
1326 return info.layoutGroupsDetails;
1327 } else if (layoutName.equals(LAYOUT_NAME_LIST)) {
1328 return info.layoutGroupsList;
1330 return new ArrayList<LayoutGroup>();
1334 public List<String> getReportNames(final String tableName) {
1335 final TableInfo info = getTableInfo(tableName);
1337 return new ArrayList<String>();
1340 return new ArrayList<String>(info.reportsMap.keySet());
1343 public Report getReport(final String tableName, final String reportName) {
1344 final TableInfo info = getTableInfo(tableName);
1349 return info.reportsMap.get(reportName);
1357 public Relationship getFieldUsedInRelationshipToOne(final String tableName, final LayoutItemField layoutField) {
1359 if (layoutField == null) {
1360 Log.error("layoutField was null");
1364 Relationship result = null;
1366 final String tableUsed = layoutField.getTableUsed(tableName);
1367 final TableInfo info = getTableInfo(tableUsed);
1369 // This table is special. We would not create a relationship to it using a field:
1370 // if(tableUsed == GLOM_STANDARD_TABLE_PREFS_TABLE_NAME)
1373 Log.error("table not found: " + tableUsed);
1377 // Look at each relationship:
1378 final String fieldName = layoutField.getName();
1379 for (final Relationship relationship : info.relationshipsMap.values()) {
1380 if (relationship != null) {
1381 // If the relationship uses the field
1382 if (StringUtils.equals(relationship.getFromField(), fieldName)) {
1383 // if the to_table is not hidden:
1384 if (!getTableIsHidden(relationship.getToTable())) {
1385 // TODO_Performance: The use of this convenience method means we get the full relationship
1386 // information again:
1387 if (getRelationshipIsToOne(tableName, relationship.getName())) {
1388 result = relationship;
1400 * @param relationshipName
1403 private boolean getRelationshipIsToOne(final String tableName, final String relationshipName) {
1404 final Relationship relationship = getRelationship(tableName, relationshipName);
1405 if (relationship != null) {
1406 final Field fieldTo = getField(relationship.getToTable(), relationship.getToField());
1407 if (fieldTo != null) {
1408 return (fieldTo.getPrimaryKey() || fieldTo.getUniqueKey());
1415 /** Get the relationship by name for a table.
1418 * @param relationshipName
1421 public Relationship getRelationship(final String tableName, final String relationshipName) {
1422 final TableInfo info = getTableInfo(tableName);
1424 Log.error("table not found: " + tableName);
1428 return info.relationshipsMap.get(relationshipName);
1434 * @param relationship
1438 public TableToViewDetails getPortalSuitableTableToViewDetails(final LayoutItemPortal portal) {
1439 UsesRelationship navigationRelationship = null;
1441 // Check whether a relationship was specified:
1442 if (portal.getNavigationType() == NavigationType.NAVIGATION_AUTOMATIC) {
1443 navigationRelationship = getPortalNavigationRelationshipAutomatic(portal);
1445 navigationRelationship = portal.getNavigationRelationshipSpecific();
1448 // Get the navigation table name from the chosen relationship:
1449 final String directlyRelatedTableName = portal.getTableUsed("" /* not relevant */);
1451 // The navigation_table_name (and therefore, the table_name output parameter,
1452 // as well) stays empty if the navrel type was set to none.
1453 String navigationTableName = null;
1454 if (navigationRelationship != null) {
1455 navigationTableName = navigationRelationship.getTableUsed(directlyRelatedTableName);
1456 } else if (portal.getNavigationType() != NavigationType.NAVIGATION_NONE) {
1457 // An empty result from get_portal_navigation_relationship_automatic() or
1458 // get_navigation_relationship_specific() means we should use the directly related table:
1459 navigationTableName = directlyRelatedTableName;
1462 if (StringUtils.isEmpty(navigationTableName)) {
1467 Log.error("document is null.");
1471 if (getTableIsHidden(navigationTableName)) {
1472 Log.error("navigation_table_name indicates a hidden table: " + navigationTableName);
1476 final TableToViewDetails result = new TableToViewDetails();
1477 result.tableName = navigationTableName;
1478 result.usesRelationship = navigationRelationship;
1487 private UsesRelationship getPortalNavigationRelationshipAutomatic(final LayoutItemPortal portal) {
1492 // If the related table is not hidden then we can just navigate to that:
1493 final String direct_related_table_name = portal.getTableUsed("" /* parent table - not relevant */);
1494 if (!getTableIsHidden(direct_related_table_name)) {
1495 // Non-hidden tables can just be shown directly. Navigate to it:
1498 // If the related table is hidden,
1499 // then find a suitable related non-hidden table by finding the first layout field that mentions one:
1500 final LayoutItemField field = getPortalFieldIsFromNonHiddenRelatedRecord(portal);
1501 if (field != null) {
1502 return field; // Returns the UsesRelationship base part. (A relationship belonging to the portal's
1505 // Instead, find a key field that's used in a relationship,
1506 // and pretend that we are showing the to field as a related field:
1507 final Relationship fieldIndentifies = getPortalFieldIdentifiesNonHiddenRelatedRecord(portal);
1508 if (fieldIndentifies != null) {
1509 final UsesRelationship result = new UsesRelationshipImpl();
1510 result.setRelationship(fieldIndentifies);
1516 // There was no suitable related table to show:
1525 private LayoutItemField getPortalFieldIsFromNonHiddenRelatedRecord(final LayoutItemPortal portal) {
1526 // Find the first field that is from a non-hidden related table.
1532 final LayoutItemField result = null;
1534 final String parent_table_name = portal.getTableUsed("" /* parent table - not relevant */);
1536 final List<LayoutItem> items = portal.getItems();
1537 for (final LayoutItem item : items) {
1538 if (item instanceof LayoutItemField) {
1539 final LayoutItemField field = (LayoutItemField) item;
1540 if (field.getHasRelationshipName()) {
1541 final String table_name = field.getTableUsed(parent_table_name);
1542 if (!(getTableIsHidden(table_name))) {
1553 * @param used_in_relationship
1558 private Relationship getPortalFieldIdentifiesNonHiddenRelatedRecord(final LayoutItemPortal portal) {
1559 // Find the first field that is from a non-hidden related table.
1562 Log.error("document is null");
1566 final String parent_table_name = portal.getTableUsed("" /* parent table - not relevant */);
1568 final List<LayoutItem> items = portal.getItems();
1569 for (final LayoutItem item : items) {
1570 if (item instanceof LayoutItemField) {
1571 final LayoutItemField field = (LayoutItemField) item;
1572 if (field.getHasRelationshipName()) {
1573 final Relationship relationship = getFieldUsedInRelationshipToOne(parent_table_name, field);
1574 if (relationship != null) {
1575 final String table_name = relationship.getToTable();
1576 if (!StringUtils.isEmpty(table_name)) {
1577 if (!(getTableIsHidden(table_name))) {
1578 return relationship;
1592 * @return The destination table name for navigation.
1594 public String getLayoutItemFieldShouldHaveNavigation(final String tableName, final LayoutItemField layoutItem) {
1595 if (StringUtils.isEmpty(tableName)) {
1599 if (layoutItem == null) {
1603 // Check whether the field controls a relationship,
1604 // meaning it identifies a record in another table.
1605 final Relationship fieldUsedInRelationshipToOne = getFieldUsedInRelationshipToOne(tableName, layoutItem);
1606 if (fieldUsedInRelationshipToOne != null) {
1607 return fieldUsedInRelationshipToOne.getToTable();
1610 // Check whether the field identifies a record in another table
1611 // just because it is a primary key in that table:
1612 final Field fieldInfo = layoutItem.getFullFieldDetails();
1613 final boolean fieldIsRelatedPrimaryKey = layoutItem.getHasRelationshipName() && (fieldInfo != null)
1614 && fieldInfo.getPrimaryKey();
1615 if (fieldIsRelatedPrimaryKey) {
1616 return layoutItem.getRelationship().getToTable();
1625 public void setIsExampleFile(final boolean isExample) {
1626 this.isExample = isExample;
1631 public boolean getIsExampleFile() {
1638 public boolean save() {
1639 final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
1640 DocumentBuilder documentBuilder = null;
1642 documentBuilder = dbf.newDocumentBuilder();
1643 } catch (final ParserConfigurationException e) {
1644 // TODO Auto-generated catch block
1645 e.printStackTrace();
1649 final org.w3c.dom.Document doc = documentBuilder.newDocument();
1650 final Element rootNode = doc.createElement(NODE_ROOT);
1651 doc.appendChild(rootNode);
1653 rootNode.setAttribute(ATTRIBUTE_TITLE, databaseTitle.getTitleOriginal());
1654 rootNode.setAttribute(ATTRIBUTE_TRANSLATION_ORIGINAL_LOCALE, translationOriginalLocale);
1655 setAttributeAsBoolean(rootNode, ATTRIBUTE_IS_EXAMPLE, isExample);
1657 String strHostingMode = "";
1658 if (hostingMode == HostingMode.HOSTING_MODE_POSTGRES_CENTRAL) {
1659 strHostingMode = "postgres_central";
1660 } else if (hostingMode == HostingMode.HOSTING_MODE_SQLITE) {
1661 strHostingMode = "sqlite";
1663 strHostingMode = "postgres_self";
1665 final Element nodeConnection = createElement(doc, rootNode, NODE_CONNECTION);
1666 nodeConnection.setAttribute(ATTRIBUTE_CONNECTION_HOSTING_MODE, strHostingMode);
1667 nodeConnection.setAttribute(ATTRIBUTE_CONNECTION_SERVER, connectionServer);
1668 nodeConnection.setAttribute(ATTRIBUTE_CONNECTION_DATABASE, connectionDatabase);
1669 setAttributeAsDecimal(nodeConnection, ATTRIBUTE_CONNECTION_PORT, connectionPort);
1672 for (final TableInfo table : tablesMap.values()) {
1673 final Element nodeTable = createElement(doc, rootNode, NODE_TABLE);
1674 saveTableNodeBasic(doc, nodeTable, table);
1675 saveTableLayouts(doc, nodeTable, table);
1678 final TransformerFactory transformerFactory = TransformerFactory.newInstance();
1679 Transformer transformer;
1681 transformer = transformerFactory.newTransformer();
1682 } catch (final TransformerConfigurationException e) {
1683 // TODO Auto-generated catch block
1684 e.printStackTrace();
1688 // TODO: This probably distorts text nodes,
1689 // so careful when we load/save them. For instance, scripts.
1690 transformer.setOutputProperty(OutputKeys.INDENT, "yes");
1691 transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
1693 // Make sure that the parent directory exists:
1694 final File file = new File(fileURI);
1696 Files.createParentDirs(file);
1697 } catch (final IOException e) {
1698 e.printStackTrace();
1702 final DOMSource source = new DOMSource(doc);
1703 final StreamResult result = new StreamResult(file);
1705 // Output to console for testing
1706 // StreamResult result = new StreamResult(System.out);
1709 transformer.transform(source, result);
1710 } catch (final TransformerException e) {
1711 // TODO Auto-generated catch block
1712 e.printStackTrace();
1724 private void saveTableLayouts(final org.w3c.dom.Document doc, final Element tableNode, final TableInfo table) {
1726 final Element layoutsNode = createElement(doc, tableNode, NODE_DATA_LAYOUTS);
1728 final Element nodeLayoutDetails = createElement(doc, layoutsNode, NODE_DATA_LAYOUT);
1729 nodeLayoutDetails.setAttribute(ATTRIBUTE_NAME, LAYOUT_NAME_DETAILS);
1730 saveLayoutNode(doc, nodeLayoutDetails, table.layoutGroupsDetails);
1732 final Element nodeLayoutList = createElement(doc, layoutsNode, NODE_DATA_LAYOUT);
1733 nodeLayoutList.setAttribute(ATTRIBUTE_NAME, LAYOUT_NAME_LIST);
1734 saveLayoutNode(doc, nodeLayoutList, table.layoutGroupsList);
1736 final Element reportsNode = createElement(doc, tableNode, NODE_REPORTS);
1737 for (final Report report : table.reportsMap.values()) {
1738 final Element element = createElement(doc, reportsNode, NODE_REPORT);
1739 saveReport(doc, element, report);
1749 private void saveReport(final org.w3c.dom.Document doc, final Element element, final Report report) {
1750 // TODO Auto-generated method stub
1754 private void saveLayoutNode(final org.w3c.dom.Document doc, final Element element,
1755 final List<LayoutGroup> layoutGroups) {
1756 final Element elementGroups = createElement(doc, element, NODE_DATA_LAYOUT_GROUPS);
1758 for (final LayoutGroup layoutGroup : layoutGroups) {
1759 if (layoutGroup instanceof LayoutItemNotebook) {
1760 final Element elementGroup = createElement(doc, elementGroups, NODE_DATA_LAYOUT_NOTEBOOK);
1761 saveDataLayoutGroup(doc, elementGroup, layoutGroup);
1762 } else if (layoutGroup instanceof LayoutItemPortal) {
1763 final Element elementGroup = createElement(doc, elementGroups, NODE_DATA_LAYOUT_PORTAL);
1764 saveDataLayoutPortal(doc, elementGroup, (LayoutItemPortal) layoutGroup);
1766 final Element elementGroup = createElement(doc, elementGroups, NODE_DATA_LAYOUT_GROUP);
1767 saveDataLayoutGroup(doc, elementGroup, layoutGroup);
1775 * @param elementGroup
1776 * @param layoutGroup
1778 private void saveDataLayoutPortal(final org.w3c.dom.Document doc, final Element element,
1779 final LayoutItemPortal portal) {
1780 saveUsesRelationship(element, portal);
1781 saveDataLayoutGroup(doc, element, portal);
1783 final Element elementNavigation = createElement(doc, element, NODE_DATA_LAYOUT_PORTAL_NAVIGATIONRELATIONSHIP);
1784 String navigationTypeAsString = "";
1785 switch (portal.getNavigationType()) {
1786 case NAVIGATION_AUTOMATIC:
1787 navigationTypeAsString = ATTRIBUTE_PORTAL_NAVIGATION_TYPE_AUTOMATIC;
1789 case NAVIGATION_NONE:
1790 navigationTypeAsString = ATTRIBUTE_PORTAL_NAVIGATION_TYPE_NONE;
1792 case NAVIGATION_SPECIFIC:
1793 navigationTypeAsString = ATTRIBUTE_PORTAL_NAVIGATION_TYPE_SPECIFIC;
1798 elementNavigation.setAttribute(ATTRIBUTE_PORTAL_NAVIGATION_TYPE, navigationTypeAsString);
1800 if (navigationTypeAsString.equals(ATTRIBUTE_PORTAL_NAVIGATION_TYPE_SPECIFIC)) {
1801 // Write the specified relationship name:
1802 saveUsesRelationship(elementNavigation, portal.getNavigationRelationshipSpecific());
1808 * @param elementGroup
1809 * @param layoutGroup
1811 private void saveDataLayoutGroup(final org.w3c.dom.Document doc, final Element nodeGroup, final LayoutGroup group) {
1812 saveTitle(doc, nodeGroup, group);
1814 // Write the column count:
1815 setAttributeAsDecimal(nodeGroup, ATTRIBUTE_LAYOUT_GROUP_COLUMNS_COUNT, group.getColumnCount());
1817 // Write the child items:
1818 for (final LayoutItem layoutItem : group.getItems()) {
1819 if (layoutItem instanceof LayoutItemPortal) {
1820 final Element element = createElement(doc, nodeGroup, NODE_DATA_LAYOUT_PORTAL);
1821 saveDataLayoutPortal(doc, element, (LayoutItemPortal) layoutItem);
1822 } else if (layoutItem instanceof LayoutItemNotebook) {
1823 final Element element = createElement(doc, nodeGroup, NODE_DATA_LAYOUT_NOTEBOOK);
1824 saveDataLayoutGroup(doc, element, (LayoutItemNotebook) layoutItem);
1825 } else if (layoutItem instanceof LayoutGroup) {
1826 final Element element = createElement(doc, nodeGroup, NODE_DATA_LAYOUT_GROUP);
1827 saveDataLayoutGroup(doc, element, (LayoutGroup) layoutItem);
1828 } else if (layoutItem instanceof LayoutItemField) {
1829 final Element element = createElement(doc, nodeGroup, NODE_DATA_LAYOUT_ITEM);
1830 saveDataLayoutItemField(doc, element, (LayoutItemField) layoutItem);
1831 } else if (layoutItem instanceof LayoutItemGroupBy) {
1832 final Element element = createElement(doc, nodeGroup, NODE_DATA_LAYOUT_ITEM_GROUPBY);
1833 saveDataLayoutItemGroupBy(doc, element, (LayoutItemGroupBy) layoutItem);
1843 private void saveDataLayoutItemGroupBy(final org.w3c.dom.Document doc, final Element element,
1844 final LayoutItemGroupBy item) {
1845 saveDataLayoutGroup(doc, element, item);
1847 final Element elementGroupBy = createElement(doc, element, NODE_GROUPBY);
1848 saveDataLayoutItemField(doc, elementGroupBy, item.getFieldGroupBy());
1850 final Element elementSecondaryFields = createElement(doc, element, NODE_SECONDARY_FIELDS);
1851 final Element elementLayoutGroup = createElement(doc, elementSecondaryFields, NODE_DATA_LAYOUT_GROUP);
1852 saveDataLayoutGroup(doc, elementLayoutGroup, item.getSecondaryFields());
1860 private void saveDataLayoutItemField(final org.w3c.dom.Document doc, final Element element,
1861 final LayoutItemField item) {
1862 element.setAttribute(ATTRIBUTE_NAME, item.getName());
1863 saveUsesRelationship(element, item);
1865 final CustomTitle customTitle = item.getCustomTitle();
1866 if (customTitle != null) {
1867 final Element elementCustomTitle = createElement(doc, element, NODE_CUSTOM_TITLE);
1868 setAttributeAsBoolean(elementCustomTitle, ATTRIBUTE_CUSTOM_TITLE_USE_CUSTOM,
1869 customTitle.getUseCustomTitle());
1870 saveTitle(doc, elementCustomTitle, customTitle); // LayoutItemField doesn't use its own title member.
1873 setAttributeAsBoolean(element, ATTRIBUTE_USE_DEFAULT_FORMATTING, item.getUseDefaultFormatting());
1875 final Element elementFormatting = createElement(doc, element, NODE_FORMATTING);
1876 saveFormatting(elementFormatting, item.getFormatting());
1883 private void saveUsesRelationship(final Element element, final UsesRelationship item) {
1884 final Relationship relationship = item.getRelationship();
1885 if (relationship != null) {
1886 element.setAttribute(ATTRIBUTE_RELATIONSHIP_NAME, relationship.getName());
1889 final Relationship relatedRelationship = item.getRelatedRelationship();
1890 if (relatedRelationship != null) {
1891 element.setAttribute(ATTRIBUTE_RELATED_RELATIONSHIP_NAME, relatedRelationship.getName());
1897 * @param nodeConnection
1900 private Element createElement(final org.w3c.dom.Document doc, final Element parentNode, final String name) {
1901 final Element node = doc.createElement(name);
1902 parentNode.appendChild(node);
1906 public String getSelfHostedDirectoryPath() {
1907 final String uriFile = getFileURI();
1908 if (!StringUtils.isEmpty(uriFile)) {
1909 final File file = new File(uriFile);
1910 final File parent = file.getParentFile();
1911 if (parent == null) {
1916 File dataDir = null;
1917 switch (hostingMode) {
1918 case HOSTING_MODE_POSTGRES_SELF:
1919 dataDir = new File(parent, "glom_postgres_data");
1921 case HOSTING_MODE_POSTGRES_CENTRAL:
1924 case HOSTING_MODE_SQLITE:
1932 if (dataDir != null) {
1933 return dataDir.getPath();
1937 // TODO: std::cerr << G_STRFUNC << ": returning empty string." << std::endl;
1943 public void setConnectionDatabase(final String databaseName) {
1944 connectionDatabase = databaseName;
1949 * Gets the primary key Field for the specified table name.
1952 * name of table to search for the primary key field
1953 * @return primary key Field
1955 public Field getTablePrimaryKeyField(final String tableName) {
1956 Field primaryKey = null;
1957 final List<Field> fieldsVec = getTableFields(tableName);
1958 for (int i = 0; i < Utils.safeLongToInt(fieldsVec.size()); i++) {
1959 final Field field = fieldsVec.get(i);
1960 if (field.getPrimaryKey()) {
1970 * @param attrDocumentID
1975 * @throws IOException
1977 public LayoutItem getLayoutItemByPath(
1978 final String tableName, final String layoutName, final String layoutPath) throws IOException {
1979 final List<LayoutGroup> listLayoutGroups = getDataLayoutGroups(layoutName, tableName);
1980 if(listLayoutGroups == null) {
1981 Log.error("The layout with the specified name was not found. tableName=" + tableName + ", layoutName=" + layoutName);
1985 if(listLayoutGroups.isEmpty()) {
1986 Log.error("The layout was empty. attrTableName=" + tableName + ", layoutName=" + layoutName);
1990 final int[] indices = Utils.parseLayoutPath(layoutPath);
1991 if((indices == null) || (indices.length == 0)) {
1992 Log.error("The layout path was empty or could not be parsed. layoutPath=" + layoutPath);
1996 LayoutItem item = null;
1998 for(int index:indices) {
2000 Log.error("An index in the layout path was negative, at depth=" + depth + ", layoutPath=" + layoutPath);
2004 //Get the nth item of either the top-level list or the current item:
2006 if(index < listLayoutGroups.size()) {
2007 item = listLayoutGroups.get(index);
2009 Log.error("An index in the layout path is larger than the number of child items, at depth=" + depth + ", layoutPath=" + layoutPath);
2013 if(item instanceof LayoutGroup) {
2014 final LayoutGroup group = (LayoutGroup)item;
2015 final List<LayoutItem> items = group.getItems();
2016 if(index < items.size()) {
2017 item = items.get(index);
2019 Log.error("An index in the layout path is larger than the number of child items, at depth=" + depth + ", layoutPath=" + layoutPath);
2023 Log.error("An intermediate item in the layout path is not a layout group, at depth=" + depth + ", layoutPath=" + layoutPath);
2032 Log.error("The item specifed by the layout path could not be found. layoutPath=" + layoutPath);