DataSetSDOHelper.java
/*
** Module : DataSetSDOHelper.java
** Abstract : Helper APIs to work with DataSet in Tuscany.
**
** Copyright (c) 2019-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description----------------------------------------
** 001 CA 20190717 Created initial version.
** 002 CA 20191119 Added support for before-table.
** CA 20191125 Added support for relations.
** 003 OM 20200120 Small typo fix.
** 004 CA 20200923 API improvements. Added extent support, other data-types. Allow access to the hidden
** temp-table fields.
** OM 20201120 Added support for marshaling "xmlNodeName" and "errorString" attributes.
** CA 20210831 Track the created record in the before-table, if change logging is enabled.
** CA 20210910 Changes to allow support for before-table returned from the remote request via an
** OUTPUT/INPUT-OUTPUT table or dataset.
** CA 20220104 Added support for denormalized extent fields, for .NET client compatibility.
** CA 20220215 When creating a row, populate the extent fields with NULL values.
** CA 20220216 Fixed issues with denormalized extent field names: they can collide with other legacy
** field names so the changes ensure they are unique.
** CA 20220428 BigDecimal and GregorianCalendar are correct data-types for decimal and date(time(tz)).
** CA 20220513 Added 'getTableIndex' helpers.
** CA 20220524 Added helpers which work with table metadata, to retrieve field information.
** CA 20220527 Added helpers for relation metadata.
** CA 20220604 Replaced the FWD proxy with the Java proxy, to allow easy usage from Tomcat.
** CA 20220830 Performance improvements for management of the table metadata (especially related to
** computing the SDO types associated with the table).
** CA 20220930 Rows can be added multiple times, tuscany SDO will take care of not re-adding an existing
** row.
** CA 20221006 Use a copy of the row instances associated with a meta field, when caching.
** CA 20221116 Added 'unknownValuesForFields', a flag which when set, will force the DataObject.get to
** return null even for Java native types - without this flag, DataObject.get will return
** i.e. 0 or false for int/boolean native types.
** 005 CA 20230221 Encapsulated the field and relation metadata in their own classes, to avoid the DataObject
** overhead and increase performance. Other performance optimizations.
** 006 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
** 007 CA 20230511 Backed out the HELPER_CONTEXT singleton, as is not thread-safe.
** 008 CA 20240305 Added namespace URI, namespace prefix and XML node-name for table/graph metadata in
** OpenClient.
** 009 CA 20240331 Use logical constants for TRUE, FALSE, UNKNOWN, within internal FWD runtime, so logical
** type checks must include sub-classes, too.
** 010 CA 20240426 For OUTPUT dataset/table, if the argument has already the metadata set, re-initialize this
** metadata with the actual response argument, while keeping the metadata references intact.
** 011 CA 20240707 Performance improvement when building an output table parameter: use a list to collect the
** rows, and set them only once, as adding them one-by-one is expensive.
** 012 LS 20240807 Updated initGraphMeta so that the comparison between the tables is made based on the
** structure and not on the name.
** LS 20240819 Added 'mergeTables', a flag which when set, will merge the response instead of replacing
** it in the dataset.
** 013 SP 20240926 createBeforeRow() should not set the beforeRecord._peerRowId if state is ROW_DELETED.
** 014 CA 20241107 Added character constant support.
** 015 AS 20250213 Added APIs for index management.
** 016 AS 20250217 Resolve field names case insensitively for data object get/set/unset operations.
** 017 ICP 20250123 Added support for integer and int64 constants, fixed javadoc.
** 018 AP 20250411 Added more tests and improved support for denormalized extents.
** CA 20250424 'forcedNormalized' is used by FWD runtime, so normalized access needs to be allowed even
** when using denormalized extents.
** CA 20250424 .NET compatibiltiy still uses normalized extent fields when transferring them.
** 019 AS 20250507 Added field caching for DataObjects.
** 020 AS 20250604 Made HelperContext scope a thread local class member.
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
**
** Additional terms under GNU Affero GPL version 3 section 7:
**
** Under Section 7 of the GNU Affero GPL version 3, the following additional
** terms apply to the works covered under the License. These additional terms
** are non-permissive additional terms allowed under Section 7 of the GNU
** Affero GPL version 3 and may not be removed by you.
**
** 0. Attribution Requirement.
**
** You must preserve all legal notices or author attributions in the covered
** work or Appropriate Legal Notices displayed by works containing the covered
** work. You may not remove from the covered work any author or developer
** credit already included within the covered work.
**
** 1. No License To Use Trademarks.
**
** This license does not grant any license or rights to use the trademarks
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
** of Golden Code Development Corporation. You are not authorized to use the
** name Golden Code, FWD, or the names of any author or contributor, for
** publicity purposes without written authorization.
**
** 2. No Misrepresentation of Affiliation.
**
** You may not represent yourself as Golden Code Development Corporation or FWD.
**
** You may not represent yourself for publicity purposes as associated with
** Golden Code Development Corporation, FWD, or any author or contributor to
** the covered work, without written authorization.
**
** 3. No Misrepresentation of Source or Origin.
**
** You may not represent the covered work as solely your work. All modified
** versions of the covered work must be marked in a reasonable way to make it
** clear that the modified work is not originating from Golden Code Development
** Corporation or FWD. All modified versions must contain the notices of
** attribution required in this license.
*/
package com.goldencode.p2j.util;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.math.*;
import java.util.*;
import java.util.logging.*;
import com.goldencode.p2j.util.logging.*;
import com.goldencode.util.*;
import org.apache.tuscany.sdo.api.*;
import com.goldencode.p2j.persist.*;
import commonj.sdo.*;
import commonj.sdo.helper.*;
/**
* Defines APIs to load DATASETs as Tuscany SDO. This allows compatibility with the legacy Java Open client.
* <p>
* The {@link com.goldencode.p2j.persist.DataSet} is constructed as a {@link DataGraph}, for which
* the metadata has the following structure (each graph node is a {@link DataObject}):
* <ul>
* <li>the root object has a <code>graph</code> type, with two properties:
* <ol>
* <li><code>scope</code>, which holds the {@link HelperContext} used to build the metadata.
* </li>
* <li><code>metadata</code>, having the custom <code>graphmetadata</code> type.</li>
* </ol>
* </li>
* <li><code>graphmetadata</code> defines the details about the tables and relations, in these properties:
* <ol>
* <li><code>scope</code>, which helps the {@link HelperContext} used to build the metadata</li>
* <li><code>name</code>, a String with the DataSet name</li>
* <li><code>xmlns</code>, a String with the XML namespace.</li>
* <li><code>xmlPrefix</code>, a String with the XML prefix.</li>
* <li><code>xmlNodeName</code>, a String with the XML node name.</li>
* <li><code>idSequence</code>, the internal record ID sequence</li>
* <li><code>tables</code>, a list of <code>tablemetadata</code> holding the defined tables</li>
* <li><code>relations</code>, a list of {@link LegacyRelationMetaData} holding the defined relations</li>
* </ol>
* </li>
* <li><code>tablemetadata</code> defines the details about a table:
* <ol>
* <li><code>idx</code>, an int with the table index in the datagraph's table list.</li>
* <li><code>name</code>, a String with the legacy table name.</li>
* <li><code>numFields</code>, an int with the number of defined fields.</li>
* <li><code>b4img</code>, a boolean flag identifying if a table as a BEFORE-IMAGE.</li>
* <li><code>numIndexes</code>, an int identifying the number of defined indexes.</li>
* <li><code>indexes</code>, a String with the index definitions.</li>
* <li><code>xmlns</code>, a String with the XML namespace.</li>
* <li><code>xmlPrefix</code>, a String with the XML prefix.</li>
* <li><code>xmlNodeName</code>, a String with the XML node name.</li>
* <li><code>errorString</code>, a String with the table's ERROR-STRING attribute value.</li>
* <li><code>tabletype</code>, a {@link Type} defining this table (used to add rows to the table). This
* type MUST extend the <code>baserowtype</code>, so that the rows know their container.
* </li>
* <li><code>fields</code>, a list with the field definitions, of {@link LegacyFieldMetaData} type.</li>
* <li><code>fieldMap</code>, a case-insensitive map with a cache of {@link LegacyFieldMetaData} for
* each defined field</li>
* <li><code>extent</code>, a boolean flag indicating that at least one field is extent</li>
* <li><code>rows</code>, a list with the table's rows (they have as type a custom type
* build based on the table's<code>fields</code>. The base type is
* <code>baserowtype</code>, an empty type.
* </li>
* <li><code>beforeRows</code>, a list with the before-table's rows, with the same record
* type as the <code>rows</code> list. These are created/managed only when logging is enabled.
* </li>
* <li><code>normalizedExtent</code>, flag indicating if the extent fields are kept in a single field
* (normalized, as for Java clients) or denormalized, with individual fields for each index (as for
* .NET clients).
* </li>
* </ol>
* </li>
* <li>{@link LegacyRelationMetaData} defines the details about a relation:
* <ol>
* <li><code>name</code>, a String with the relation name.</li>
* <li><code>parentBuffer</code>, a String with the name of the parent table.</li>
* <li><code>childBuffer</code>, a String with the name of the child table.</li>
* <li><code>whereString</code>, a String with the WHERE clause of this relation.</li>
* <li><code>active</code>, a Boolean with the relation's active state.</li>
* <li><code>parentId</code>, a Boolean with the parent-id state.</li>
* <li><code>pairs</code>, a String with <code>parent, child</code> field pairs, comma delimited.</li>
* <li><code>fkeyHidden</code>, a Boolean with the FOREIGN-KEY-HIDDEN status.</li>
* <li><code>recursive</code>, a Boolean with the recursive status.</li>
* <li><code>nested</code>, a Boolean with the NESTED status.</li>
* <li><code>reposition</code>, a Boolean with Rthe REPOSITION status.</li>
* </ol>
* </li>
* <li> {@link LegacyFieldMetaData} defines the details about a field:
* <ol>
* <li><code>idx</code>, an int with the field's position in the table's <code>fields</code></li>
* <li><code>name</code>, a String with the legacy field name; indexed for denormalized extent.</li>
* <li><code>extent</code>, an int with the field's extent.</li>
* <li><code>extentIndex</code>, an int with the extent field's index, for denormalized extent.</li>
* <li><code>extentField</code>, the legacy name of the extent field (non-indexed).</li>
* <li><code>type</code>, a {@link BaseDataType} class with the field's type.</li>
* <li><code>legacytype</code>, an int with the field's type, as specified by
* {@link LegacyJavaAppserver} constants.</li>
* <li><code>order</code>, an int with the fields order.</li>
* <li><code>xmlMapping</code>, a int with the XML name describing this field.</li>
* </ol>
* </li>
* </ul>
* TODO: The XML related metadata is NOT used now in {@link LegacyJavaAppserver}.
*/
class DataSetSDOHelper
{
/** The URI for the custom types. */
public static final String FWD_URI = "goldencode.com";
/** The URI for defining known types (as int, String, etc). */
public static final String SDO_API_URI = "commonj.sdo";
/** A prefix added to denormalized extent fields. */
public static final String DENORMALIZED_PREFIX = "__fwd_denormalized__";
/** The suffix for a table row.*/
private static final String FWD_TABLE_TYPE_SUFFIX = "---FWD-type" + (char) 2;
/** Logger */
private static final CentralLogger LOG = CentralLogger.get(DataSetSDOHelper.class);
/**
* Flag indicating any unset field will return null, instead of its default value (like 0 and false for
* Java native types).
*/
private static volatile boolean unknownValuesForFields = false;
/**
* Flag indicating the response will be merged, instead of being replaced in the dataset.
*/
private static volatile boolean mergeTables = false;
/**
* Thread-local helper context for the data object.
*/
private static final ThreadLocal<HelperContext> threadLocalScope = ThreadLocal.withInitial(() ->
{
HelperContext context = SDOUtil.createHelperContext();
createGraphMetaDataTypes(context);
return context;
});
/**
* Set the {@link #unknownValuesForFields} flag.
*
* @param state
* <code>true</code> to return null if the field is not set.
*/
public static void setUnknownValuesForFields(boolean state)
{
unknownValuesForFields = state;
}
/**
* Get the {@link #unknownValuesForFields} flag.
*
* @return See above.
*/
public static boolean isUnknownValuesForFields()
{
return unknownValuesForFields;
}
/**
* Set the {@link #mergeTables} flag.
*
* @param state
* <code>true</code> to merge the response.
*/
public static void setMergeTables(boolean state)
{
mergeTables = state;
}
/**
* Get the {@link #mergeTables} flag.
*
* @return See above.
*/
public static boolean isMergeTables()
{
return mergeTables;
}
/**
* Begin the logging for changes in this graph.
*
* @param graph
* The graph to track changes.
*/
public static void beginLogging(DataGraph graph)
{
ChangeSummary cs = graph.getChangeSummary();
cs.beginLogging();
// clean up before-table records and start logging
String[] tables = getTableNames(graph);
for (String table : tables)
{
List<DataObject> rows = getDataObjects(graph, table);
for (DataObject row : rows)
{
FWDDataObjectType fwdRow = (FWDDataObjectType) row;
fwdRow.beginLogging();
}
List<DataObject> beforeRows = getBeforeObjects(graph, table);
for (DataObject row : beforeRows)
{
row.delete();
}
}
}
/**
* End the logging for changes in this graph.
*
* @param graph
* The graph to track changes.
*/
public static void endLogging(DataGraph graph)
{
ChangeSummary cs = graph.getChangeSummary();
cs.endLogging();
}
/**
* Get the graph name from the specified metadata, of type <code>graphmetadata</code>.
*
* @param metadata
* The metadata.
*
* @return The graph name.
*/
public static String getDataGraphName(DataObject metadata)
{
return metadata.getString("name");
}
/**
* Get the metadata object for this graph, from the <code>metadata</code> property of the root
* object.
*
* @param graph
* The graph.
*
* @return The metadata object.
*/
public static DataObject getMetaData(DataGraph graph)
{
return graph.getRootObject().getDataObject("metadata");
}
/**
* Add a new new field with the specified configuration.
*
* @param fields
* The table's fields list, as retrieved from the {@link DataObject}.
* @param metaData
* The table metadata, of type <code>tablemetadata</code>. The field will be added
* to the <code>fields</code> property.
* @param field
* The field meta.
*
* @return The created field metadata.
*/
public static LegacyFieldMetaData setFieldMetaData(List<LegacyFieldMetaData> fields,
DataObject metaData,
LegacyFieldMetaData field)
{
clearMap("fieldMap", metaData);
if (field.extent > 0)
{
metaData.setBoolean("extent", true);
boolean normalizedExtent = metaData.getBoolean("normalizedExtent");
if (normalizedExtent)
{
// nothing to do for normalized extent (Java-style clients)
fields.add(field);
return field;
}
// for denormalized extent (.NET-style clients), create a field for each index
for (int fidx = 1; fidx <= field.extent; fidx++)
{
String fname = buildDenormalizedFieldName(field.name, fidx);
LegacyFieldMetaData extFieldMeta = setFieldMetaData(metaData,
field.idx,
fname,
0,
field.type,
field.legacyType,
field.order,
field.xmlMapping);
extFieldMeta.extentIndex = fidx;
extFieldMeta.extentField = field.name;
}
}
else
{
fields.add(field);
}
return field;
}
/**
* Add a new new field with the specified configuration.
*
* @param metaData
* The table metadata, of type <code>tablemetadata</code>. The field will be added
* to the <code>fields</code> property.
* @param idx
* The field's index in the metadata.
* @param name
* The field's name.
* @param extent
* The field's extent.
* @param cls
* The fields {@link BaseDataType} class.
* @param legacyType
* The legacy type (numeric value based on {@link LegacyJavaAppserver} constants.
* @param order
* The field's order.
* @param xmlMapping
* The field's mapping.
*
* @return The created field metadata.
*/
public static LegacyFieldMetaData setFieldMetaData(DataObject metaData,
int idx,
String name,
int extent,
Class<?> cls,
int legacyType,
int order,
int xmlMapping)
{
if (extent > 0)
{
metaData.setBoolean("extent", true);
}
// check if the field name is unique
List<LegacyFieldMetaData> fields = metaData.getList("fields");
for (int i = 0; i < fields.size(); i++)
{
LegacyFieldMetaData f = fields.get(i);
if (name.equalsIgnoreCase(f.name))
{
String tname = getTableName(metaData);
throw new IllegalStateException("Field " + name + " already exists in table " + tname + "!");
}
};
LegacyFieldMetaData fieldMeta =
new LegacyFieldMetaData(idx, name, extent, cls, legacyType, order, xmlMapping);
return setFieldMetaData(fields, metaData, fieldMeta);
}
/**
* Get the relation metadata with the given name.
*
* @param graph
* The data graph.
* @param name
* The relation name.
*
* @return The relation metadata, or <code>null</code> if it does not exist.
*/
public static LegacyRelationMetaData getRelation(DataGraph graph, String name)
{
return getRelation(getMetaData(graph), name);
}
/**
* Get the relation metadata with the given name.
*
* @param metaData
* The graph metadata.
* @param name
* The relation name.
*
* @return The relation metadata, or <code>null</code> if it does not exist.
*/
public static LegacyRelationMetaData getRelation(DataObject metaData, String name)
{
List<LegacyRelationMetaData> relations = metaData.getList("relations");
for (LegacyRelationMetaData rel : relations)
{
if (rel.getName().equalsIgnoreCase(name))
{
return rel;
}
}
return null;
}
/**
* Get the all relation metadata from the given graph.
*
* @param graph
* The data graph.
*
* @return The relations metadata.
*/
public static LegacyRelationMetaData[] getRelations(DataGraph graph)
{
return getRelations(getMetaData(graph));
}
/**
* Get the all relation metadata from the given graph.
*
* @param metaData
* The graph metadata.
*
* @return The relations metadata.
*/
public static LegacyRelationMetaData[] getRelations(DataObject metaData)
{
List<LegacyRelationMetaData> relations = metaData.getList("relations");
LegacyRelationMetaData[] res = new LegacyRelationMetaData[relations.size()];
for (int i = 0; i < relations.size(); i++)
{
res[i] = relations.get(i);
}
return res;
}
/**
* Add a relation to the graph's metadata.
*
* @param metaData
* The graph metadata.
* @param name
* The relation name.
* @param parentBuffer
* The parent buffer.
* @param childBuffer
* The child buffer.
* @param whereString
* The where string.
* @param active
* Flag indicating if this relation is active.
* @param parentId
* Flag indicating if parent-id is used.
* @param pairs
* The field pairs, in <code>parent, child</code> format.
* @param fkeyHidden
* Flag indicating if fkey-hidden is used.
* @param recursive
* Flag indicating if recursive is used.
* @param nested
* Flag indicating if nested is used.
* @param reposition
* Flag indicating if reposition is used.
*/
public static void addRelation(DataObject metaData,
String name,
String parentBuffer,
String childBuffer,
String whereString,
boolean active,
boolean parentId,
String pairs,
boolean fkeyHidden,
boolean recursive,
boolean nested,
boolean reposition)
{
LegacyRelationMetaData rel = new LegacyRelationMetaData(name,
parentBuffer,
childBuffer,
whereString,
active,
parentId,
pairs,
fkeyHidden,
recursive,
nested,
reposition);
metaData.getList("relations").add(rel);
}
/**
* Create an object with a table metadata. This object will have the {@code tablemetadata} type and is not
* attached to the graph.
*
* @param name
* The table's name.
* @param numFields
* The number of fields.
* @param b4img
* Flag indicating if the table has a BEFORE-IMAGE.
* @param numIndexes
* The number of indexes.
* @param indexes
* The index specification (TODO: not validated/implemented at this time).
* @param xmlns
* The XML namespace (TODO: not validated/implemented at this time).
* @param xmlPrefix
* The XML prefix (TODO: not validated/implemented at this time).
* @param xmlNodeName
* The XML node name (TODO: not validated/implemented at this time).
* @param errorString
* The table's ERROR-STRING attribute (TODO: not validated/implemented at this time).
* @param normalizedExtent
* Flag indicating if the extents are normalized or individual fields will be created for each
* index.
*
* @return An object with the table metadata.
*/
public static DataObject createTableMetaData(String name,
int numFields,
boolean b4img,
int numIndexes,
String indexes,
String xmlns,
String xmlPrefix,
String xmlNodeName,
String errorString,
boolean normalizedExtent)
{
HelperContext scope = getHelperContext();
DataFactory dataFactory = scope.getDataFactory();
DataObject tableMeta = dataFactory.create(FWD_URI, "tablemetadata");
initTableMetaData(tableMeta,
name,
numFields,
b4img,
numIndexes,
indexes,
xmlns,
xmlPrefix,
xmlNodeName,
errorString,
normalizedExtent);
return tableMeta;
}
/**
* Initialize the given table metadata.
*
* @param tableMeta
* The table's metadata object.
* @param name
* The table's name.
* @param numFields
* The number of fields.
* @param b4img
* Flag indicating if the table has a BEFORE-IMAGE.
* @param numIndexes
* The number of indexes.
* @param indexes
* The index specification (TODO: not validated/implemented at this time).
* @param xmlns
* The XML namespace (TODO: not validated/implemented at this time).
* @param xmlPrefix
* The XML prefix (TODO: not validated/implemented at this time).
* @param xmlNodeName
* The XML node name (TODO: not validated/implemented at this time).
* @param errorString
* The table's ERROR-STRING attribute (TODO: not validated/implemented at this time).
* @param normalizedExtent
* Flag indicating if the extents are normalized or individual fields will be created for each
* index.
*/
public static void initTableMetaData(DataObject tableMeta,
String name,
int numFields,
boolean b4img,
int numIndexes,
String indexes,
String xmlns,
String xmlPrefix,
String xmlNodeName,
String errorString,
boolean normalizedExtent)
{
tableMeta.setString("name", name);
tableMeta.setInt("numFields", numFields);
tableMeta.setBoolean("b4img", b4img);
tableMeta.setInt("numIndexes", numIndexes);
tableMeta.setString("indexes", indexes);
tableMeta.setString("xmlns", xmlns);
tableMeta.setString("xmlPrefix", xmlPrefix);
tableMeta.setString("xmlNodeName", xmlNodeName);
tableMeta.setString("errorString", errorString);
tableMeta.setBoolean("normalizedExtent", normalizedExtent);
tableMeta.getList("fields").clear();
tableMeta.getList("rows").clear();
tableMeta.getList("beforeRows").clear();
tableMeta.setBoolean("extent", false);
clearMap("fieldMap", tableMeta);
}
/**
* Create LegacyIndexDefinition objects from the formatted string in table meta
* containing all index information, components, primary/unique flags etc.
*
* @param tableMeta
* The table's metadata object.
* @return A list of LegacyIndexDefinition objects holding the metadata for each index
*/
public static List<LegacyIndexDefinition> getLegacyIndexDefinition(DataObject tableMeta)
{
String indexes = tableMeta.getString("indexes");
if(indexes == null || indexes.isEmpty())
{
throw new IllegalArgumentException("Null or empty indexes string is not allowed.");
}
List<LegacyIndexDefinition> indexList = new ArrayList<>();
indexList.add(new LegacyIndexDefinition());
StringBuilder sb = new StringBuilder();
List<String> components = new ArrayList<>();
components.add(new String());
int i = 2;
if(indexes.charAt(0) == '1')
{
indexList.get(0).setPrimaryUnique(true);
}
else if (indexes.charAt(0) == '0')
{
indexList.get(0).setPrimaryUnique(false);
}
else
{
i = 0;
}
int componentIndex = 0;
int indexPosition = 0;
int length = indexes.length();
for (; i < length; i++)
{
if(indexes.charAt(i) == ' ')
{
continue;
}
if (indexes.charAt(i) == '.' || i == length - 1)
{
if(i == length - 1)
{
sb.append(indexes.charAt(i));
}
indexList.get(indexPosition).setLegacyName(sb.toString());
if (i != length - 1)
{
components.add(new String());
indexList.add(new LegacyIndexDefinition());
indexPosition++;
sb.setLength(0);
continue;
}
}
if (indexes.charAt(i) == ',')
{
components.set(componentIndex, sb.toString());
components.add(new String());
sb.setLength(0);
componentIndex++;
continue;
}
if (indexes.charAt(i) == ':')
{
components.set(componentIndex, sb.toString());
indexList.get(indexPosition).setComponents(new ArrayList<>(components));
components.clear();
componentIndex = 0;
sb.setLength(0);
continue;
}
sb.append(indexes.charAt(i));
}
return indexList;
}
/**
* A method that returns the table's primary unique index.
*
* @param tableMeta
* The table's metadata object.
*/
public static LegacyIndexDefinition getPrimaryUniqueIndex(DataObject tableMeta)
{
String indexes = tableMeta.getString("indexes");
LegacyIndexDefinition led = getLegacyIndexDefinition(tableMeta).get(0);
if (led.isPrimaryUnique())
{
return led;
}
return null;
}
/**
* A method that returns the LegacyIndexDefinition that has the specified legacy name.
* The requested index can be primary or just unique.
*
* @param indexName
* The string legacy name of the index.
* @param tableMeta
* The table's metadata object.
* @return The requested LegacyIndexDefinition object.
*/
public static LegacyIndexDefinition getIndex(String indexName, DataObject tableMeta)
{
if (indexName == null || indexName.isEmpty() || tableMeta == null)
{
throw new IllegalArgumentException("Null/empty primary unique index or " +
"data object meta data not allowed.");
}
List<LegacyIndexDefinition> indexes = getLegacyIndexDefinition(tableMeta);
for (LegacyIndexDefinition index : indexes)
{
if (index.getLegacyName().equalsIgnoreCase(indexName))
{
return index;
}
}
return null;
}
/**
* A method that marks an index as primary unique.
* The index should already be part of the indexes.
* The previous primary index will be set as just unique and
* will be swapped in the indexes with the newly set primary index.
*
* @param primaryUniqueIndexName
* Index legacy name that should be set as primary unique.
* @param tableMeta
* The table's metadata object.
* @return True if the primary unique index was set, or false if one was already present.
*/
public static boolean setPrimaryUniqueIndex(String primaryUniqueIndexName, DataObject tableMeta)
{
if (primaryUniqueIndexName == null || tableMeta == null)
{
throw new IllegalArgumentException("Null primary unique index or " +
"data object meta data not allowed.");
}
List<LegacyIndexDefinition> indexes = getLegacyIndexDefinition(tableMeta);
LegacyIndexDefinition primaryUniqueIndex = getIndex(primaryUniqueIndexName, tableMeta);
int existingIndexPosition = -1;
existingIndexPosition = indexes.indexOf(primaryUniqueIndex);
if (existingIndexPosition >= 0)
{
if (primaryUniqueIndex.isPrimaryUnique())
{
return false;
}
else
{
indexes.get(0).setPrimaryUnique(false);
Collections.swap(indexes, 0, existingIndexPosition);
indexes.get(0).setPrimaryUnique(true);
}
}
else
{
throw new IllegalArgumentException("Index not found. To set an index as primary unique, " +
"it should be present in indexes.");
}
tableMeta.setString("indexes", toFormattedString(indexes));
return true;
}
/**
* A method that adds an index to indexes.
*
* @param index
* The index to be added. Must not be primary!
* @param tableMeta
* The table's metadata object.
* @return True if the index was added successfully.
*/
public static boolean addIndex(LegacyIndexDefinition index, DataObject tableMeta)
{
if (index == null || tableMeta == null)
{
throw new IllegalArgumentException("Null unique index or " +
"data object meta data not allowed.");
}
if (index.isPrimaryUnique())
{
throw new IllegalArgumentException("Adding primary indexes not allowed.");
}
List<LegacyIndexDefinition> indexes = getLegacyIndexDefinition(tableMeta);
if (indexes.contains(index))
{
return false;
}
else
{
indexes.add(index);
tableMeta.setString("indexes", toFormattedString(indexes));
return true;
}
}
/**
* A method that removes any index from the indexes.
*
* @param indexName
* Legacy name of the index to be removed.
* @param tableMeta
* The table's metadata object.
*/
public static void removeIndex(String indexName, DataObject tableMeta)
{
if (indexName == null || indexName.isEmpty() || tableMeta == null)
{
throw new IllegalArgumentException("Null/empty unique index or " +
"data object meta data not allowed.");
}
List<LegacyIndexDefinition> indexes = getLegacyIndexDefinition(tableMeta);
for (LegacyIndexDefinition index : indexes)
{
if (index.getLegacyName().equalsIgnoreCase(indexName))
{
indexes.remove(index);
tableMeta.setString("indexes", toFormattedString(indexes));
return;
}
}
}
/**
* A method that adds a component (field) to the specified index.
*
* @param indexName
* Legacy name of the index.
* @param field
* The field to be added as a new component.
* @param tableMeta
* The table's metadata object.
* @return True if the new component was successfully added.
*/
public static boolean addComponentToIndex(String indexName, String field, DataObject tableMeta)
{
if (indexName == null ||
indexName.isEmpty() ||
tableMeta == null ||
field == null ||
field.isEmpty())
{
throw new IllegalArgumentException("Null/empty arguments not allowed.");
}
if (!isTableField(tableMeta, field))
{
throw new IllegalArgumentException("Field " + field + " not found in table " +
tableMeta.getString("name"));
}
List<LegacyIndexDefinition> indexes = getLegacyIndexDefinition(tableMeta);
for (LegacyIndexDefinition index : indexes)
{
if (index.getLegacyName().equalsIgnoreCase(indexName))
{
index.getComponents().add(field);
tableMeta.setString("indexes", toFormattedString(indexes));
return true;
}
}
return false;
}
/**
* A method that removes a component (field) to the specified index.
*
* @param indexName
* Legacy name of the index.
* @param field
* The field to be removed from the index.
* @param tableMeta
* The table's metadata object.
* @return True if the new component was successfully removed.
*/
public static boolean removeComponentFromIndex(String indexName, String field, DataObject tableMeta)
{
if (indexName == null ||
indexName.isEmpty() ||
tableMeta == null ||
field == null ||
field.isEmpty())
{
throw new IllegalArgumentException("Null/empty arguments not allowed.");
}
if (!isTableField(tableMeta, field))
{
throw new IllegalArgumentException("Field " + field + " not found in table " +
tableMeta.getString("name"));
}
List<LegacyIndexDefinition> indexes = getLegacyIndexDefinition(tableMeta);
for (LegacyIndexDefinition index : indexes)
{
if (index.getLegacyName().equalsIgnoreCase(indexName))
{
boolean removed = index.getComponents().remove(field);
tableMeta.setString("indexes", toFormattedString(indexes));
return removed;
}
}
return false;
}
/**
* A method that converts a List of IndexDefinition
* objects to single a formatted string represtation.
*
* @param indexes
* A list o IndexDefinition containing all the index information.
* @return Formatted string that contains all the index information.
* The format is specified in progress documentation:
* [primeUniqueFlag,primeFld1[,primeFldn]...:primeIdxName.]
*/
public static String toFormattedString(List<LegacyIndexDefinition> indexes)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < indexes.size(); i++)
{
if (indexes.get(i).isPrimaryUnique() && i == 0)
{
sb.append("1,");
}
else if (i == 0)
{
sb.append("0,");
}
for (String component: indexes.get(i).getComponents())
{
sb.append(component);
sb.append(',');
}
sb.setCharAt(sb.length() - 1, ':');
sb.append(indexes.get(i).getLegacyName());
sb.append('.');
}
sb.setLength(sb.length() - 1);
return sb.toString();
}
/**
* Add the specified table metadata to the graph. This will create a custom type based on the
* configured fields, so that each row will be created with this type.
*
* @param metaData
* The graph metadata.
* @param table
* The table metadata.
*/
public static void addTableMetaData(DataObject metaData, DataObject table)
{
boolean sameGraph = table.getContainer() == metaData;
if (table.getContainer() != null && !sameGraph)
{
List<LegacyFieldMetaData> fields = getFieldMetaData(table);
String name = table.getString("name");
int numFields = table.getInt("numFields");
boolean b4img = table.getBoolean("b4img");
int numIndexes = table.getInt("numIndexes");
String indexes = table.getString("indexes");
String xmlns = table.getString("xmlns");
String xmlPrefix = table.getString("xmlPrefix");
String xmlNodeName = table.getString("xmlNodeName");
String errorString = table.getString("errorString");
boolean normalizedExtent = table.getBoolean("normalizedExtent");
table = createTableMetaData(name, numFields, b4img, numIndexes, indexes, xmlns, xmlPrefix,
xmlNodeName, errorString, normalizedExtent);
List<LegacyFieldMetaData> fieldList = table.getList("fields");
for (int i = 0; i < fields.size(); i++)
{
LegacyFieldMetaData fieldMeta = fields.get(i);
setFieldMetaData(fieldList, table, fieldMeta);
}
}
HelperContext scope = (HelperContext) metaData.get("scope");
TypeHelper typeHelper = scope.getTypeHelper();
List<LegacyFieldMetaData> fields = table.getList("fields"); // direct access, all fields are needed
if (fields.isEmpty())
{
return;
}
String tableName = table.getString("name");
if (!sameGraph)
{
// set the table index
table.setInt("idx", metaData.getList("tables").size());
metaData.getList("tables").add(table);
}
String tableTypeName = tableTypeName(tableName, fields);
Type type = typeHelper.getType(FWD_URI, tableTypeName);
if (type == null)
{
Type stringType = typeHelper.getType(SDO_API_URI, "String");
Type booleanType = typeHelper.getType(SDO_API_URI, "Boolean");
Type intType = typeHelper.getType(SDO_API_URI, "Int");
Type longType = typeHelper.getType(SDO_API_URI, "Long");
Type doubleType = typeHelper.getType(SDO_API_URI, "Double");
Type dateType = typeHelper.getType(SDO_API_URI, "Date");
Type objectType = typeHelper.getType(SDO_API_URI, "Object");
Type bytesType = typeHelper.getType(SDO_API_URI, "Bytes");
DataObject tableType = createTypeDescription(scope, FWD_URI, tableTypeName);
// the table must be derived from 'baserowtype' - otherwise the rows will not know the
// container, and will not be able to access the table metadata
Type baseRowType = typeHelper.getType(FWD_URI, "baserowtype");
tableType.set("baseType", Arrays.asList(baseRowType));
for (int i = 0; i < fields.size(); i++)
{
LegacyFieldMetaData field = fields.get(i);
Class<?> cls = field.type;
Type ftype = objectType;
if (cls == integer.class || cls == integer.integerConstant.class)
{
ftype = intType;
}
else if (cls == int64.class || cls == int64.int64Constant.class)
{
ftype = longType;
}
else if (cls == decimal.class)
{
ftype = objectType;
}
else if (cls == logical.class || cls == logical.logicalConstant.class)
{
ftype = booleanType;
}
else if (cls == character.class ||
cls == longchar.class ||
cls == character.characterConstant.class)
{
ftype = stringType;
}
else if (cls == date.class || cls == datetime.class || cls == datetimetz.class)
{
ftype = objectType;
}
else if (cls == memptr.class || cls == blob.class)
{
ftype = bytesType;
}
DataObject prop = addPropertyDescription(tableType, ftype, field.name);
int extent = field.extent;
if (extent != 0 && extent != SourceNameMapper.NO_EXTENT)
{
prop.setBoolean("many", true);
}
}
type = typeHelper.define(tableType);
}
table.set("tabletype", type);
}
/**
* Get the metadata for all fields in the specified table.
* <p>
* This is always in normalized mode, even if the extents are denormalized.
*
* @param tableMeta
* The table metadata.
*
* @return A list of field metadata objects.
*/
public static List<LegacyFieldMetaData> getFieldMetaData(DataObject tableMeta)
{
return getFieldMetaData(tableMeta, true);
}
/**
* Get the metadata for all fields in the specified table.
*
* @param tableMeta
* The table metadata.
* @param normalized
* If the fields are required in normalized or denormalized mode.
*
* @return A list of field metadata objects.
*/
public static List<LegacyFieldMetaData> getFieldMetaData(DataObject tableMeta, boolean normalized)
{
List<LegacyFieldMetaData> allFields = tableMeta.getList("fields");
boolean isExtent = tableMeta.getBoolean("extent");
if (normalized && isExtent)
{
// do not include synthetic extent fields for denormalized extent
List<LegacyFieldMetaData> legacyFields = new ArrayList<>(allFields.size());
for (int i = 0; i < allFields.size(); i++)
{
LegacyFieldMetaData field = allFields.get(i);
if (field.extentField == null)
{
legacyFields.add(field);
}
else
{
int extent = 0;
while (i < allFields.size() && field.extentField.equalsIgnoreCase(allFields.get(i).extentField))
{
i = i + 1;
extent = extent + 1;
}
i = i - 1;
LegacyFieldMetaData fext = new LegacyFieldMetaData(field.idx,
field.extentField,
extent,
field.type,
field.legacyType,
field.order,
field.xmlMapping);
legacyFields.add(fext);
}
}
return legacyFields;
}
else
{
return new ArrayList<>(allFields);
}
}
/**
* Get the field metadata for the specified property.
*
* @param tableMeta
* The table metadata.
* @param fname
* The field name.
* @param forceNormalizedFields
* When this flag is set, only normalized extent fields are looked.
*
* @return The found field's metadata.
*
* @throws IllegalStateException
* If the field can't be resolved.
*/
public static LegacyFieldMetaData getFieldMetaData(DataObject tableMeta,
String fname,
boolean forceNormalizedFields)
{
return getFieldMetaData(tableMeta, fname, forceNormalizedFields, true);
}
/**
* Get the field metadata for the specified property.
*
* @param tableMeta
* The table metadata.
* @param fname
* The field name. The comparison is case insensitive.
* @param forceNormalizedFields
* When this flag is set, only normalized extent fields are looked.
* @param failNotFound
* Throw an {@link IllegalStateException} if the field can't be resolved.
*
* @return The found field's metadata.
*
* @throws IllegalStateException
* If the field can't be resolved and <code>failNotFound</code> flag is set.
*/
public static LegacyFieldMetaData getFieldMetaData(DataObject tableMeta,
String fname,
boolean forceNormalizedFields,
boolean failNotFound)
{
Map<String, LegacyFieldMetaData> fieldMap = (Map<String, LegacyFieldMetaData>)
tableMeta.get("fieldMap");
if (fieldMap.isEmpty())
{
List<LegacyFieldMetaData> fields = tableMeta.getList("fields");
for (LegacyFieldMetaData field : fields)
{
fieldMap.put(field.getName(), field);
}
}
boolean normalizedExtent = tableMeta.getBoolean("normalizedExtent");
boolean extent = tableMeta.getBoolean("extent");
if (extent && !normalizedExtent && !forceNormalizedFields)
{
// if denormalized extent access is done outside of the FWD runtime,
// then the lookup for denormalized extent fields has priority
LegacyFieldMetaData f = fieldMap.get(DENORMALIZED_PREFIX + fname);
if (f != null && f.extentIndex != null)
{
return f;
}
}
// look for actual name match
LegacyFieldMetaData f = fieldMap.get(fname);
if (f != null)
{
return f;
}
String tname = getTableName(tableMeta);
String msg = "Could not resolve field " + fname + " in table " + tname + "!";
if (failNotFound)
{
throw new IllegalStateException(msg);
}
else if (LOG.isLoggable(Level.WARNING))
{
LOG.log(Level.WARNING, msg);
}
return null;
}
/**
* Get the metadata for the table registered on the given index in the graph.
*
* @param metaData
* The graph metadata.
* @param i
* The table index.
*
* @return The table metadata.
*/
public static DataObject getTableMetaData(DataObject metaData, int i)
{
return (DataObject) metaData.getList("tables").get(i);
}
/**
* Get the metadata for the table registered with the given name in the graph.
*
* @param metaData
* The graph metadata.
* @param name
* The table name.
*
* @return The table metadata.
*/
public static DataObject getTableMetaData(DataObject metaData, String name)
{
List<DataObject> tables = metaData.getList("tables");
for (int i = 0; i < tables.size(); i++)
{
DataObject tableMeta = tables.get(i);
String tname = tableMeta.getString("name");
if (tname.equalsIgnoreCase(name))
{
return tableMeta;
}
}
return null;
}
/**
* Get the metadata for the specified table.
*
* @param graph
* The graph.
* @param table
* The table name.
*
* @return The table metadata or <code>null</code> if the table is not part of this graph.
*/
public static DataObject getTableMetaData(DataGraph graph, String table)
{
return getTableMetaData(getMetaData(graph), table);
}
/**
* Get the metadata list for all tables.
*
* @param graph
* The graph.
*
* @return The metadata for all tables.
*/
public static List<DataObject> getTableMetaData(DataGraph graph)
{
List<DataObject> tables = new ArrayList<>();
String[] tableNames = getTableNames(graph);
for (String table : tableNames)
{
tables.add(getTableMetaData(graph, table));
}
return tables;
}
/**
* Create a new graph to hold a dataset. It holds just the root <code>graph</code> node.
*
* @return A graph with no metadata.
*/
public static DataGraph createDataGraph()
{
HelperContext scope = getHelperContext();
TypeHelper typeHelper = scope.getTypeHelper();
DataGraph graph = SDOUtil.createDataGraph();
DataObject graphRoot = graph.createRootObject(typeHelper.getType(FWD_URI, "graph"));
graphRoot.set("scope", scope);
return graph;
}
/**
* Create a new metadata object to hold the table details.
*
* @param metadata
* The root graph node.
*
* @return The graph metadata object.
*/
public static DataGraph createDataGraph(DataObject metadata)
{
if (metadata.getContainer() != null)
{
throw new IllegalStateException("Metadata is already attached!");
}
HelperContext scope = (HelperContext) metadata.get("scope");
TypeHelper typeHelper = scope.getTypeHelper();
DataGraph graph = SDOUtil.createDataGraph();
DataObject graphRoot = graph.createRootObject(typeHelper.getType(FWD_URI, "graph"));
graphRoot.setDataObject("metadata", metadata);
return graph;
}
/**
* Set the metadata to the specified graph.
*
* @param graph
* The graph object.
* @param metadata
* The metadata object which will be set to the graph's <code>metadata</code>
* property.
*/
public static void setGraphMetaData(DataGraph graph, DataObject metadata)
{
if (metadata.getContainer() != null)
{
throw new IllegalStateException("Metadata is already attached!");
}
DataObject graphRoot = graph.getRootObject();
graphRoot.setDataObject("metadata", metadata);
}
/**
* Create a metadata object with the specified name.
*
* @param name
* The graph name.
*
* @return The metadata object. Will be attached to a graph via {@link #setGraphMetaData}.
*/
public static DataObject createGraphMetaData(String name)
{
return createGraphMetaData(name, null, null, null);
}
/**
* Create a metadata object with the specified details.
*
* @param name
* The graph name.
* @param xmlns
* The namespace URI.
* @param xmlPrefix
* The namespace prefix.
* @param xmlNodeName
* The XML node-name.
*
* @return The metadata object. Will be attached to a graph via {@link #setGraphMetaData}.
*/
public static DataObject createGraphMetaData(String name,
String xmlns,
String xmlPrefix,
String xmlNodeName)
{
HelperContext scope = getHelperContext();
DataFactory dataFactory = scope.getDataFactory();
DataObject metadata = dataFactory.create(FWD_URI, "graphmetadata");
metadata.set("scope", scope);
initGraphMetaData(metadata, name, xmlns, xmlPrefix, xmlNodeName, (List<String>) null);
return metadata;
}
/**
* Initialize this graph metadata.
*
* @param metadata
* The graph metadata.
* @param name
* The graph name.
* @param xmlns
* The namespace URI.
* @param xmlPrefix
* The namespace prefix.
* @param xmlNodeName
* The XML node-name.
* @param tableNames
* The table names to keep in this metadata. May be {@code null}.
*/
public static void initGraphMetaData(DataObject metadata,
String name,
String xmlns,
String xmlPrefix,
String xmlNodeName,
List<String> tableNames)
{
metadata.setString("name", name);
if (xmlns != null)
{
metadata.setString("xmlns", xmlns);
}
else
{
metadata.unset("xmlns");
}
if (xmlPrefix != null)
{
metadata.setString("xmlPrefix", xmlPrefix);
}
else
{
metadata.unset("xmlPrefix");
}
if (xmlNodeName != null)
{
metadata.setString("xmlNodeName", xmlNodeName);
}
else
{
metadata.unset("xmlPrefix");
}
metadata.setLong("idSequence", 0);
metadata.getList("relations").clear();
if (tableNames == null)
{
metadata.getList("tables").clear();
}
else
{
HelperContext scope = getHelperContext();
DataFactory dataFactory = scope.getDataFactory();
List<DataObject> tables = metadata.getList("tables");
List<DataObject> argumentTables = new ArrayList<>(tables);
// clear and fix the tables
tables.clear();
for (int i = 0; i < tableNames.size(); i++)
{
String tableName = tableNames.get(i);
// find the DataObject for this table
DataObject table = null;
for (int j = 0; j < argumentTables.size(); j++)
{
DataObject argTable = argumentTables.get(j);
String argTableName = argTable.getString("name");
if (argTableName.equalsIgnoreCase(tableName))
{
table = argTable;
break;
}
}
if (table == null)
{
// build a new metadata and set its name
table = dataFactory.create(FWD_URI, "tablemetadata");
table.setString("name", tableName);
}
// reset the metadata
table.setInt("idx", tables.size());
table.getList("fields").clear();
clearMap("fieldMap", table);
table.setBoolean("extent", false);
table.getList("rows").clear();
table.getList("beforeRows").clear();
// add it to the graph
tables.add(table);
}
}
}
/**
* Initialize this graph metadata.
*
* @param metadata
* The graph metadata.
* @param name
* The graph name.
* @param xmlns
* The namespace URI.
* @param xmlPrefix
* The namespace prefix.
* @param xmlNodeName
* The XML node-name.
* @param tableSignatures
* The table names and signatures to keep in this metadata. May be {@code null}.
*
* @return A map with old and new name pairs.
*/
public static Map<String, String> initGraphMetaData(DataObject metadata,
String name,
String xmlns,
String xmlPrefix,
String xmlNodeName,
Map<String, List<Map<String, String>>> tableSignatures)
{
Map<String, String> namePairs = new LinkedHashMap<>();
metadata.setString("name", name);
if (xmlns != null)
{
metadata.setString("xmlns", xmlns);
}
else
{
metadata.unset("xmlns");
}
if (xmlPrefix != null)
{
metadata.setString("xmlPrefix", xmlPrefix);
}
else
{
metadata.unset("xmlPrefix");
}
if (xmlNodeName != null)
{
metadata.setString("xmlNodeName", xmlNodeName);
}
else
{
metadata.unset("xmlPrefix");
}
metadata.setLong("idSequence", 0);
metadata.getList("relations").clear();
if (tableSignatures == null)
{
metadata.getList("tables").clear();
}
else
{
HelperContext scope = getHelperContext();
DataFactory dataFactory = scope.getDataFactory();
List<DataObject> tables = metadata.getList("tables");
List<DataObject> argumentTables = new ArrayList<>(tables);
// clear and fix the tables
tables.clear();
for (int i = 0; i < argumentTables.size(); i++)
{
DataObject argTable = argumentTables.get(i);
List<LegacyFieldMetaData> fields = argTable.getList("fields");
List<Map<String, String>> argTableSignature = new LinkedList<>();
//create the signature for current table
for (int j = 0; j < fields.size(); j++)
{
LegacyFieldMetaData fieldMetaData = fields.get(j);
String extent = String.valueOf(fieldMetaData.getExtent());
String type = fieldMetaData.getType().getTypeName();
Map<String, String> currentField = new LinkedHashMap<>();
currentField.put("type", type);
currentField.put("extent", extent);
argTableSignature.add(currentField);
}
// check if there is a table that matches the signature of the argTable
// and save the new name
DataObject table = null;
for (Map.Entry<String, List<Map<String, String>>> tableSignature : tableSignatures.entrySet())
{
if (tableSignature.getValue().equals(argTableSignature))
{
String argTableName = argTable.getString("name");
String tableName = tableSignature.getKey();
if (!argTableName.equals(tableName))
{
namePairs.put(tableName, argTableName);
}
table = argTable;
tableSignatures.remove(tableName);
break;
}
}
if (table == null)
{
// build a new metadata and set its name
table = dataFactory.create(FWD_URI, "tablemetadata");
table.setString("name", argTable.getString("name"));
}
// reset the metadata
table.setInt("idx", tables.size());
table.getList("fields").clear();
clearMap("fieldMap", table);
table.setBoolean("extent", false);
table.getList("rows").clear();
table.getList("beforeRows").clear();
// add it to the graph
tables.add(table);
}
}
return namePairs;
}
/**
* A method that clears a map property, if not set, instantiates one and sets it.
* This method emulates the behavior of 'getList' for maps.
*
* @param map
* The name of the map property.
* @param table
* Table metadata object.
*/
private static void clearMap(String map, DataObject table)
{
Object value = table.get(map);
if (value == null)
{
Map<String, Object> newMap = new CaseInsensitiveHashMap<>(100);
table.set(map, newMap);
}
else if (value instanceof Map)
{
((Map<?, ?>) value).clear();
}
}
/**
* Create a metadata object identical with the given metadata (except tables).
*
* @param ori
* The source metadata.
*
* @return The metadata object. Will be attached to a graph via {@link #setGraphMetaData}.
*/
public static DataObject createGraphMetaData(DataObject ori)
{
HelperContext scope = getHelperContext();
DataFactory dataFactory = scope.getDataFactory();
DataObject metadata = dataFactory.create(FWD_URI, "graphmetadata");
metadata.set("scope", scope);
metadata.setString("name", ori.getString("name"));
if (ori.isSet("xmlns"))
{
metadata.setString("xmlns", ori.getString("xmlns"));
}
if (ori.isSet("xmlPrefix"))
{
metadata.setString("xmlPrefix", ori.getString("xmlPrefix"));
}
if (ori.isSet("xmlNodeName"))
{
metadata.setString("xmlNodeName", ori.getString("xmlNodeName"));
}
metadata.setLong("idSequence", 0);
return metadata;
}
/**
* Create the metadata types.
*
* @param scope
* The helper context where the types will be defined.
*/
public static void createGraphMetaDataTypes(HelperContext scope)
{
TypeHelper typeHelper = scope.getTypeHelper();
Type stringType = typeHelper.getType(SDO_API_URI, "String");
Type booleanType = typeHelper.getType(SDO_API_URI, "Boolean");
Type intType = typeHelper.getType(SDO_API_URI, "Int");
Type longType = typeHelper.getType(SDO_API_URI, "Long");
Type objectType = typeHelper.getType(SDO_API_URI, "Object");
/*
graph:
scope: HelperContext
metadata: graphmetadata
graphmetadata:
scope: HelperContext
name: String
xmlns: String
xmlPrefix: String
xmlNodeName: String
tables[]: tablemetadata
relations[]: relationmetadata
tablemetadata:
idx: int
name: string
numFields: int
b4img: boolean
numIndexes: int
indexes: String
xmlns: String
xmlPrefix: String
xmlNodeName: String
errorString: String
tabletype: object
fields[]: fieldmetadata
fieldMap: CaseInsensitiveHashMap<String, LegacyFieldMetadata>
extent: boolean
rows[]: baserowtype
beforeRows[]: baserowtype
normalizedExtent: boolean
relationmetadata:
name: string
parentBuffer: string
childBuffer: string
whereString: string
active: boolean
parentId: boolean
pairs: string
fkeyHidden: boolean
recursive: boolean
nested: boolean
reposition: boolean
fieldmetadata:
idx: int
name: String
extent: int
extentIndex: int
extentField: String
type: Class
legacytype: type
order: int
xmlMapping: int
*/
DataObject baseRowType = createTypeDescription(scope, FWD_URI, "baserowtype");
typeHelper.define(baseRowType);
DataObject tableMeta = createTypeDescription(scope, FWD_URI, "tablemetadata");
addPropertyDescription(tableMeta, intType, "idx");
addPropertyDescription(tableMeta, stringType, "name");
addPropertyDescription(tableMeta, intType, "numFields");
addPropertyDescription(tableMeta, booleanType, "b4img");
addPropertyDescription(tableMeta, intType, "numIndexes");
addPropertyDescription(tableMeta, stringType, "indexes");
addPropertyDescription(tableMeta, stringType, "xmlns");
addPropertyDescription(tableMeta, stringType, "xmlPrefix");
addPropertyDescription(tableMeta, stringType, "xmlNodeName");
addPropertyDescription(tableMeta, stringType, "errorString");
addPropertyDescription(tableMeta, booleanType, "normalizedExtent");
addPropertyDescription(tableMeta, booleanType, "extent");
addPropertyDescription(tableMeta, objectType, "fieldMap");
addPropertyDescription(tableMeta, objectType, "tabletype");
DataObject fields = addPropertyDescription(tableMeta, objectType, "fields");
fields.setBoolean("many", true);
DataObject rows = addPropertyDescription(tableMeta, baseRowType, "rows");
rows.setBoolean("many", true);
DataObject beforeRows = addPropertyDescription(tableMeta, baseRowType, "beforeRows");
beforeRows.setBoolean("many", true);
typeHelper.define(tableMeta);
DataObject graphMeta = createTypeDescription(scope, FWD_URI, "graphmetadata");
addPropertyDescription(graphMeta, objectType, "scope");
addPropertyDescription(graphMeta, stringType, "name");
addPropertyDescription(graphMeta, stringType, "xmlns");
addPropertyDescription(graphMeta, stringType, "xmlPrefix");
addPropertyDescription(graphMeta, stringType, "xmlNodeName");
addPropertyDescription(graphMeta, longType, "idSequence");
DataObject tables = addPropertyDescription(graphMeta, tableMeta, "tables");
tables.setBoolean("many", true);
DataObject relations = addPropertyDescription(graphMeta, objectType, "relations");
relations.setBoolean("many", true);
typeHelper.define(graphMeta);
DataObject graphType = createTypeDescription(scope, FWD_URI, "graph");
addPropertyDescription(graphType, objectType, "scope");
addPropertyDescription(graphType, graphMeta, "metadata");
typeHelper.define(graphType);
}
/**
* Clear the rows in the entire dataset.
*
* @param metadata
* The metadata for this dataset.
*/
public static void clearAllRows(DataObject metadata)
{
List<DataObject> tables = metadata.getList("tables");
for (int i = 0; i < tables.size(); i++)
{
DataObject table = tables.get(i);
table.getList("rows").clear();
table.getList("beforeRows").clear();
}
}
/**
* Get all table names from the given graph.
*
* @param graph
* The graph.
*
* @return See above.
*/
public static String[] getTableNames(DataGraph graph)
{
return getTableNames(getMetaData(graph));
}
/**
* Get all table names from the given graph metadata.
*
* @param metadata
* The graph metadata.
*
* @return See above.
*/
public static String[] getTableNames(DataObject metadata)
{
List<DataObject> tables = metadata.getList("tables");
String[] names = new String[tables.size()];
for (int i = 0; i < names.length; i++)
{
names[i] = tables.get(i).getString("name");
}
return names;
}
/**
* Get the number of tables for a graph metadata.
*
* @param metadata
* The graph metadata.
*
* @return See above.
*/
public static int getNumTables(DataObject metadata)
{
List<DataObject> tables = metadata.getList("tables");
return tables.size();
}
/**
* Get the table name for a given row or table metadata.
*
* @param value
* The table row or table metadata.
*
* @return The table name.
*/
public static String getTableName(DataObject value)
{
Type type = value.getType();
if (type != null && type.getName().indexOf(FWD_TABLE_TYPE_SUFFIX) > 0)
{
// must be a row
return tableFromType(type);
}
// must be the table metadata
return value.getString("name");
}
/**
* Get the table index for a given row or table metadata.
*
* @param value
* The table row or table metadata.
*
* @return The table index, 0-based.
*
* @throws NullPointerException
* If the row is not attached to a table.
*/
public static int getTableIndex(DataObject value)
{
Type type = value.getType();
if (type != null && type.getName().indexOf(FWD_TABLE_TYPE_SUFFIX) > 0)
{
// must be a row
value = value.getContainer();
if (value == null)
{
throw new NullPointerException("The row is not attached to a table - can't resolve the table index.");
}
}
// must be the table metadata
return value.getInt("idx");
}
/**
* Get the table index for a given table name and data graph.
*
* @param graph
* The data graph.
* @param name
* The table name.
*
* @return The table index, 0-based.
*
* @throws IllegalArgumentException
* If the table is not part of this graph.
*/
public static int getTableIndex(DataGraph graph, String name)
{
String[] tables = getTableNames(graph);
for (int i = 0; i < tables.length; i++)
{
if (tables[i].equalsIgnoreCase(name))
{
return i;
}
}
String gname = getDataGraphName(getMetaData(graph));
throw new IllegalArgumentException("Table " + name + " does not exist in graph " + gname);
}
/**
* Get the table index for a given table name and data graph metadata.
*
* @param metadata
* The graph metadata.
* @param name
* The table name.
*
* @return The table index, 0-based.
*
* @throws IllegalArgumentException
* If the table is not part of this graph.
*/
public static int getTableIndex(DataObject metadata, String name)
{
String[] tables = getTableNames(metadata);
for (int i = 0; i < tables.length; i++)
{
if (tables[i].equalsIgnoreCase(name))
{
return i;
}
}
String gname = getDataGraphName(metadata);
throw new IllegalArgumentException("Table " + name + " does not exist in graph " + gname);
}
/**
* Get the data graph name for the given table metadata.
*
* @param tableMetadata
* The table metadata.
*
* @return The data graph name or <code>null</code> if the table is not part of a graph.
*/
public static String getGraphName(DataObject tableMetadata)
{
if (tableMetadata.getContainer() != null)
{
return getDataGraphName(tableMetadata.getContainer());
}
// TODO: tables without datasets?
throw new IllegalArgumentException("The table is not part of a dataset!");
}
/**
* Get the number of fields for a given row.
*
* @param row
* The table row.
*
* @return The number of fields as specified in the table metadata.
*/
public static int getFieldCount(DataObject row)
{
DataObject tableMeta = row.getContainer();
return tableMeta.getInt("numFields");
}
/**
* Get the number of fields for the specified table meta.
*
* @param tableMeta
* The table metadata.
*
* @return The field count.
*/
public static int getTableFieldCount(DataObject tableMeta)
{
return tableMeta.getInt("numFields");
}
/**
* Get the field names for the specified table meta.
*
* @param tableMeta
* The table metadata.
*
* @return The field names.
*/
public static String[] getTableFieldNames(DataObject tableMeta)
{
List<LegacyFieldMetaData> fields = getFieldMetaData(tableMeta);
String[] res = new String[fields.size()];
for (int i = 0; i < fields.size(); i++)
{
res[i] = fields.get(i).name;
}
return res;
}
/**
* Get the name of the field on the given index in the metadata.
*
* @param row
* The table row. Must be attached to a table.
* @param i
* The field index.
*
* @return The field's name.
*/
public static String getFieldName(DataObject row, int i)
{
DataObject tableMeta = row.getContainer();
List<LegacyFieldMetaData> fields = getFieldMetaData(tableMeta);
LegacyFieldMetaData field = fields.get(i);
return field.name;
}
/**
* Get the type of the field on the given index in the metadata.
*
* @param row
* The table row. Must be attached to a table.
* @param i
* The field index.
*
* @return The field's type.
*/
public static int getFieldType(DataObject row, int i)
{
DataObject tableMeta = row.getContainer();
List<LegacyFieldMetaData> fields = getFieldMetaData(tableMeta);
LegacyFieldMetaData field = fields.get(i);
return field.legacyType;
}
/**
* Check if this table has a field defined with that name.
*
* @param tableMetadata
* The table metadata.
* @param fieldName
* The field name.
*
* @return <code>true</code> if there is a field defined with this name.
*/
public static boolean isTableField(DataObject tableMetadata, String fieldName)
{
List<LegacyFieldMetaData> fields = tableMetadata.getList("fields");
for (int i = 0; i < fields.size(); i++)
{
LegacyFieldMetaData field = fields.get(i);
if (fieldName.equalsIgnoreCase(field.name))
{
return true;
}
}
return false;
}
/**
* Get the field type.
*
* @param tableMetadata
* The table metadata.
* @param fieldName
* The field name.
*
* @return The field type.
*/
public static int getTableFieldType(DataObject tableMetadata, String fieldName)
{
List<LegacyFieldMetaData> fields = tableMetadata.getList("fields");
for (int i = 0; i < fields.size(); i++)
{
LegacyFieldMetaData field = fields.get(i);
if (fieldName.equalsIgnoreCase(field.name))
{
return field.legacyType;
}
}
throw new IllegalArgumentException("Field " + fieldName + " does not exist in " +
getTableName(tableMetadata));
}
/**
* Get the field type.
*
* @param tableMetadata
* The table metadata.
* @param fieldIndex
* The field index.
*
* @return See above.
*/
public static int getTableFieldType(DataObject tableMetadata, int fieldIndex)
{
List<LegacyFieldMetaData> fields = tableMetadata.getList("fields");
return fields.get(fieldIndex).legacyType;
}
/**
* Create a new row for the specified table. This row is not attached to the table - use
* {@link #addDataObject} to add it.
*
* @param graph
* The graph.
* @param table
* The table name.
*
* @return A new, empty, row.
*/
public static DataObject createDataObject(DataGraph graph, String table)
{
DataObject graphMeta = getMetaData(graph);
DataObject tableMeta = getTableMetaData(graphMeta, table);
return createDataObject(tableMeta);
}
/**
* Create a new row for the specified table. This row is not attached to the table - use
* {@link #addDataObject(DataObject, DataObject)} to add it.
*
* @param tableMetaData
* The table metadata.
*
* @return The new row.
*/
public static DataObject createDataObject(DataObject tableMetaData)
{
if (tableMetaData.getContainer() != null)
{
DataObject graphMeta = tableMetaData.getContainer();
String table = getTableName(tableMetaData);
HelperContext scope = (HelperContext) graphMeta.get("scope");
DataObject tableMeta = getTableMetaData(graphMeta, table);
Type type = (Type) tableMeta.get("tabletype");
DataObject row = scope.getDataFactory().create(FWD_URI, type.getName());
Set<Class<?>> ifaces = new HashSet<>();
Class<?> parent = row.getClass();
while (parent != null)
{
for (Class<?> iface : parent.getInterfaces())
{
ifaces.add(iface);
}
parent = parent.getSuperclass();
}
ifaces.add(TempTableRecord.class);
ifaces.add(FWDDataObjectType.class);
Class<?>[] iface = ifaces.toArray(new Class[0]);
InvocationHandler handler = new FWDDataObject();
ClassLoader clsLoader = DataSetSDOHelper.class.getClassLoader();
FWDDataObjectType fwdRow = (FWDDataObjectType) Proxy.newProxyInstance(clsLoader, iface, handler);
fwdRow.setRow(row);
fwdRow.setTableMetaData(tableMetaData);
fwdRow.primaryKey(getNextId(graphMeta));
List<LegacyFieldMetaData> fields = DataSetSDOHelper.getFieldMetaData(tableMetaData, false);
boolean normalizedExtent = tableMetaData.getBoolean("normalizedExtent");
if (normalizedExtent)
{
for (int i = 0; i < fields.size(); i++)
{
LegacyFieldMetaData field = fields.get(i);
if (field.extent > 0)
{
List<?> data = new ArrayList<>();
for (int j = 0; j < field.extent; j++)
{
data.add(null);
}
row.setList(field.name, data);
}
}
}
return (DataObject) fwdRow;
}
// TODO: table without datasets?
throw new IllegalArgumentException("The table is not part of a dataset!");
}
/**
* Set the table rows for the specified table.
*
* @param graph
* The graph.
* @param tableName
* The table name.
* @param rows
* The list of rows.
*/
public static void setTableRows(DataGraph graph, String tableName, List<DataObject> rows)
{
DataObject table = getTableMetaData(graph, tableName);
table.setList("rows", rows);
}
/**
* Set the before-table rows for the specified table.
*
* @param graph
* The graph.
* @param tableName
* The table name.
* @param rows
* The list of rows.
*/
public static void setBeforeRows(DataGraph graph, String tableName, List<DataObject> rows)
{
DataObject table = getTableMetaData(graph, tableName);
table.setList("beforeRows", rows);
}
/**
* Add the specified row to the graph.
*
* @param graph
* The graph.
* @param row
* The row to add to the table.
*/
public static void addDataObject(DataGraph graph, DataObject row)
{
DataObject graphMeta = getMetaData(graph);
DataObject tableMeta = getTableMetaData(graphMeta, tableFromType(row.getType()));
if (row.getContainer() != null && row.getContainer() != tableMeta)
{
row.getContainer().getList("rows").remove(row);
}
boolean added = tableMeta.getList("rows").add(row);
if (added && graph.getChangeSummary().isLogging())
{
((FWDDataObjectType) row).beginLogging();
((FWDDataObjectType) row).rowCreated();
}
}
/**
* Add the specified row to the graph.
*
* @param tableMetaData
* The table metadata.
* @param row
* The row to add to the table.
*/
public static void addDataObject(DataObject tableMetaData, DataObject row)
{
if (row.getContainer() != null && row.getContainer() != tableMetaData)
{
row.getContainer().getList("rows").remove(row);
}
boolean added = tableMetaData.getList("rows").add(row);
if (added)
{
DataObject graphMeta = tableMetaData.getContainer();
DataGraph graph = graphMeta.getDataGraph();
if (graph.getChangeSummary().isLogging())
{
((FWDDataObjectType) row).beginLogging();
((FWDDataObjectType) row).rowCreated();
}
}
}
/**
* Get all the records for the table on the specified index, in the graph metadata.
*
* @param graph
* The graph.
* @param idx
* The table index.
*
* @return The list of rows.
*/
public static List<DataObject> getDataObjects(DataGraph graph, int idx)
{
DataObject graphMeta = getMetaData(graph);
DataObject table = getTableMetaData(graphMeta, idx);
return new ArrayList<>(table.getList("rows"));
}
/**
* Get all the records for this table.
*
* @param table
* The table metadata.
*
* @return The list of rows.
*/
public static List<DataObject> getDataObjects(DataObject table)
{
return new ArrayList<>(table.getList("rows"));
}
/**
* Get the before-table rows for this after-table.
*
* @param table
* The after-table.
*
* @return The list of before-table rows.
*/
public static List<DataObject> getBeforeObjects(DataObject table)
{
return new ArrayList<>(table.getList("beforeRows"));
}
/**
* Get all the records for the table with the given name, in the graph metadata.
*
* @param graph
* The graph.
* @param name
* The table name.
*
* @return The list of rows.
*/
public static List<DataObject> getDataObjects(DataGraph graph, String name)
{
DataObject graphMeta = getMetaData(graph);
return getDataObjects(graphMeta, name);
}
/**
* Get all the records for the table with the given name, in the graph metadata.
*
* @param graphMeta
* The graph metadata.
* @param name
* The table name.
*
* @return The list of rows.
*/
public static List<DataObject> getDataObjects(DataObject graphMeta, String name)
{
DataObject table = getTableMetaData(graphMeta, name);
return new ArrayList<>(table.getList("rows"));
}
/**
* Get the before-table rows for the specified after-table.
*
* @param graph
* The graph.
* @param name
* The table name.
*
* @return The list of before-table rows.
*/
public static List<DataObject> getBeforeObjects(DataGraph graph, String name)
{
DataObject graphMeta = getMetaData(graph);
DataObject table = getTableMetaData(graphMeta, name);
return new ArrayList<>(table.getList("beforeRows"));
}
/**
* Retrieve all rows which have an exact match for the specified fields.
*
* @param tableMetadata
* The table metadata.
* @param fields
* The field list.
* @param values
* The values to be matched.
*
* @return See above.
*/
public static List<DataObject> selectRows(DataObject tableMetadata, String[] fields, Object[] values)
{
List<DataObject> res = new ArrayList<>();
List<DataObject> rows = tableMetadata.getList("rows");
for (DataObject row : rows)
{
boolean allEqual = true;
for (int i = 0; i < fields.length; i++)
{
Object val = row.get(fields[i]);
if (!((val == null && values[i] == null) || (val != null && val.equals(values[i]))))
{
allEqual = false;
break;
}
}
if (allEqual)
{
res.add(row);
}
}
return res;
}
/**
* Get the child rows for this record.
*
* @param row
* The row.
* @param relation
* The child relation.
*
* @return The child rows.
*/
public static List<DataObject> getChildRows(DataObject row, String relation)
{
DataObject tableMeta = row.getContainer();
DataObject graphMeta = tableMeta.getContainer();
List<LegacyRelationMetaData> relations = graphMeta.getList("relations");
LegacyRelationMetaData found = null;
for (LegacyRelationMetaData rel : relations)
{
String name = rel.getName();
if (name.equalsIgnoreCase(relation))
{
found = rel;
break;
}
}
if (found == null)
{
// TODO: error?
return Collections.emptyList();
}
String child = found.getChild();
String[] pairs = found.getPairs().split(","); // parent, child
List<DataObject> childRows = getDataObjects(graphMeta, child);
Object[] parentValues = new Object[pairs.length / 2];
for (int i = 0, idx = 0; i < pairs.length; i += 2, idx++)
{
parentValues[idx] = row.get(pairs[i]);
}
List<DataObject> res = new ArrayList<>();
l1: for (DataObject childRow : childRows)
{
for (int i = 1, idx = 0; i < pairs.length; i += 2, idx++)
{
if (!parentValues[idx].equals(childRow.get(pairs[i])))
{
continue l1;
}
}
// all match, get it
res.add(childRow);
}
return res;
}
/**
* Create a new type description.
*
* @param scope
* The context.
* @param uri
* The type URI.
* @param name
* The type name.
*
* @return A new object defining this type.
*/
protected static DataObject createTypeDescription(HelperContext scope, String uri, String name)
{
DataObject typeDesc = scope.getDataFactory().create(SDO_API_URI, "Type");
typeDesc.set("name", name);
typeDesc.set("uri", uri);
return typeDesc;
}
/**
* Create a new property for the specified type.
*
* @param containerType
* The object type.
* @param propertyType
* The property type.
* @param propertyName
* The property name.
*
* @return The created property.
*/
protected static DataObject addPropertyDescription(DataObject containerType,
Object propertyType,
String propertyName)
{
DataObject property = containerType.createDataObject("property");
property.set("type", propertyType);
property.setString("name", propertyName);
property.setBoolean("containment", true);
return property;
}
/**
* Compute the denormalized field name. This name will start with the {@link #DENORMALIZED_PREFIX} and
* end with the field's index.
*
* @param fname
* The legacy extent field name.
* @param idx
* The field's index.
*
* @return See above.
*/
static String buildDenormalizedFieldName(String fname, int idx)
{
return DENORMALIZED_PREFIX + fname + idx;
}
/**
* Mark this record as being changed, in the before-table.
*
* @param record
* The changed record.
*/
static void logChanged(FWDDataObjectType record)
{
record._rowState(Buffer.ROW_MODIFIED);
createBeforeRow(record, Buffer.ROW_MODIFIED);
}
/**
* Mark this record as being deleted, in the before-table.
*
* @param record
* The delete record.
*/
static void logDeleted(FWDDataObjectType record)
{
record._rowState(Buffer.ROW_DELETED);
createBeforeRow(record, Buffer.ROW_DELETED);
}
/**
* Mark this record as being created, in the before-table.
*
* @param record
* The created record.
*/
static void logCreated(FWDDataObjectType record)
{
record._rowState(Buffer.ROW_CREATED);
createBeforeRow(record, Buffer.ROW_CREATED);
}
/**
* Add a new before-table row.
*
* @param graph
* The graph.
* @param row
* The row to add.
*/
static void addBeforeObject(DataGraph graph, DataObject row)
{
if (row.getContainer() != null)
{
throw new IllegalStateException("Row is already attached!");
}
DataObject graphMeta = getMetaData(graph);
DataObject tableMeta = getTableMetaData(graphMeta, tableFromType(row.getType()));
tableMeta.getList("beforeRows").add(row);
}
/**
* Create a before-table row with the specified state.
*
* @param record
* The after-table row.
* @param state
* The record state.
*/
private static void createBeforeRow(FWDDataObjectType record, int state)
{
DataObject theRow = record.getRow();
String table = getTableName(theRow);
DataGraph graph = theRow.getDataGraph();
List<DataObject> beforeRows = getBeforeObjects(graph, table);
for (DataObject row : beforeRows)
{
Long peerRowId = ((FWDDataObjectType) row)._peerRowid();
if (peerRowId != null && peerRowId.equals(record.primaryKey()))
{
// already logged, ensure proper state
((FWDDataObjectType) row)._rowState(state);
return;
}
}
// create a copy of the record
DataObject beforeRow = createDataObject(graph, table);
List<Property> properties = theRow.getInstanceProperties();
try
{
((FWDDataObjectType) beforeRow).setForceNormalizedFields(true);
// always use normalized fields here
for (Property prop : properties)
{
beforeRow.set(prop, theRow.get(prop));
}
}
finally
{
((FWDDataObjectType) beforeRow).setForceNormalizedFields(false);
}
addBeforeObject(graph, beforeRow);
FWDDataObjectType beforeRecord = (FWDDataObjectType) beforeRow;
beforeRecord._rowState(state);
record._peerRowid(beforeRecord.primaryKey());
if (state == Buffer.ROW_DELETED)
{
// don't set the before record peer rowId because the record will be deleted anyway.
return;
}
beforeRecord._peerRowid(record.primaryKey());
}
/**
* Construct the custom type to hold records for this table.
*
* @param table
* The table name.
* @param fields
* The table's fields.
*
* @return The custom type name.
*/
private static String tableTypeName(String table, List<LegacyFieldMetaData> fields)
{
String key = table + FWD_TABLE_TYPE_SUFFIX + getFieldListKey(fields);
return key;
}
/**
* Get the key associated with a field list. This is used to compute the {@link #tableTypeName table name}.
*
* @param fields
* The field list.
*
* @return The key.
*/
private static String getFieldListKey(List<LegacyFieldMetaData> fields)
{
StringBuilder key = new StringBuilder(fields.size() * 64);
char delim1 = (char) 1;
char delim3 = (char) 3;
for (int i = 0; i < fields.size(); i++)
{
LegacyFieldMetaData field = fields.get(i);
if (field.extentIndex != null)
{
// skip the denormalized fields from the key
continue;
}
key.append(field.idx).append(delim1);
key.append(field.name).append(delim1);
key.append(field.extent).append(delim1);
key.append(field.legacyType).append(delim1);
key.append(field.order).append(delim1);
key.append(field.xmlMapping).append(delim1);
key.append(delim3);
}
return key.toString();
}
/**
* Compute the table name from the custom type of the table's records.
*
* @param type
* The table's record type.
*
* @return The table name.
*/
private static String tableFromType(Type type)
{
String name = type.getName();
return name.substring(0, name.indexOf(FWD_TABLE_TYPE_SUFFIX));
}
/**
* Get next internal record ID for this graph.
*
* @param graph
* The graph.
*
* @return The next record ID.
*/
private static long getNextId(DataGraph graph)
{
return getNextId(getMetaData(graph));
}
/**
* Get next internal record ID for this graph.
*
* @param graphMeta
* The graph metadata.
*
* @return The next record ID.
*/
private static long getNextId(DataObject graphMeta)
{
long nextId = graphMeta.getLong("idSequence");
nextId = nextId + 1;
graphMeta.setLong("idSequence", nextId);
return nextId;
}
/**
* Get the {@link HelperContext} instance.
*
* @return See above.
*/
private static HelperContext getHelperContext()
{
return threadLocalScope.get();
}
/**
* A simple test for the Tuscany SDO APIs.
*/
public static void main(String[] args)
{
boolean normalizedExtentForTt1 = false;
boolean normalizedExtentForTt2 = true;
DataObject metadata = createGraphMetaData("ds1");
DataGraph graph = createDataGraph(metadata);
DataObject tableTt1 = createTableMetaData("tt1", 6, false, 0, null, null, null, null, null,
normalizedExtentForTt1);
DataObject tableTt2 = createTableMetaData("tt2", 2, false, 0, null, null, null, null, null,
normalizedExtentForTt2);
// field index is always 1-based
setFieldMetaData(tableTt1, 1, "f1", 0, integer.class, LegacyJavaAppserver.TYPE_INTEGER, 0, 0);
setFieldMetaData(tableTt1, 2, "f2", 0, character.class, LegacyJavaAppserver.TYPE_CHARACTER, 1, 0);
setFieldMetaData(tableTt1, 3, "f3", 0, memptr.class, LegacyJavaAppserver.TYPE_MEMPTR, 2, 0);
setFieldMetaData(tableTt1, 4, "f4", 0, decimal.class, LegacyJavaAppserver.TYPE_DECIMAL, 3, 0);
setFieldMetaData(tableTt1, 5, "f5", 0, logical.class, LegacyJavaAppserver.TYPE_LOGICAL, 4, 0);
setFieldMetaData(tableTt1, 6, "f6", 2, decimal.class, LegacyJavaAppserver.TYPE_DECIMAL, 5, 0);
setFieldMetaData(tableTt1, 7, "f7", 2, decimal.class, LegacyJavaAppserver.TYPE_DECIMAL, 6, 0);
setFieldMetaData(tableTt2, 1, "f1", 0, integer.class, LegacyJavaAppserver.TYPE_INTEGER, 0, 0);
setFieldMetaData(tableTt2, 2, "f2", 2, decimal.class, LegacyJavaAppserver.TYPE_DECIMAL, 1, 0);
setFieldMetaData(tableTt2, 3, "f3", 0, date.class, LegacyJavaAppserver.TYPE_DATE, 2, 0);
setFieldMetaData(tableTt2, 4, "f4", 3, decimal.class, LegacyJavaAppserver.TYPE_DECIMAL, 3, 0);
// TODO: relations?
addTableMetaData(metadata, tableTt1);
addTableMetaData(metadata, tableTt2);
addRelation(metadata, "r1", "tt1", "tt2", "", true, false, "f1,f1", false, false, true, false);
LegacyRelationMetaData[] relations = getRelations(graph);
relations = getRelations(metadata);
for (LegacyRelationMetaData rel : relations)
{
String name = rel.getName();
String parent = rel.getParent();
String child = rel.getChild();
System.out.println("relation: " + name + " " + parent + "/" + child);
}
DataObject metadata2 = getMetaData(graph);
if (metadata != metadata2)
{
LOG.warning("false!");
}
System.out.println(getDataGraphName(metadata));
String[] names = getTableNames(graph);
for (String nm : names)
{
System.out.println(nm);
}
DataObject rtt1 = createDataObject(graph, "tt1");
// test 'forceNormalized
((FWDDataObjectType) rtt1).setForceNormalizedFields(true);
rtt1.set(3, -1.0);
rtt1.set(5, Arrays.asList(1.0, 2.0));
rtt1.set(6, Arrays.asList(3.0, 4.0));
double f4 = (double) rtt1.get(3);
List l6 = (List) rtt1.get(5);
List l7 = (List) rtt1.get(6);
assert f4 == -1;
assert (double) l6.get(0) == 1.0 && (double) l6.get(1) == 2.0;
assert (double) l7.get(0) == 3.0 && (double) l7.get(1) == 4.0;
assert rtt1.isSet(3);
assert rtt1.isSet(5);
assert rtt1.isSet(6);
rtt1.unset(3);
rtt1.unset(5);
rtt1.unset(6);
assert !rtt1.isSet(3);
assert !rtt1.isSet(5);
assert !rtt1.isSet(6);
rtt1.set(3, -2.0);
rtt1.setList(5, Arrays.asList(10.0, 20.0));
rtt1.setList(6, Arrays.asList(30.0, 40.0));
f4 = rtt1.getDouble(3);
l6 = rtt1.getList(5);
l7 = rtt1.getList(6);
assert f4 == -2;
assert (double) l6.get(0) == 10.0 && (double) l6.get(1) == 20.0;
assert (double) l7.get(0) == 30.0 && (double) l7.get(1) == 40.0;
rtt1.set("f4", -3.0);
rtt1.set("f6", Arrays.asList(11.0, 12.0));
rtt1.set("f7", Arrays.asList(13.0, 14.0));
f4 = (double) rtt1.get("f4");
l6 = (List) rtt1.get("f6");
l7 = (List) rtt1.get("f7");
assert f4 == -3;
assert (double) l6.get(0) == 11.0 && (double) l6.get(1) == 12.0;
assert (double) l7.get(0) == 13.0 && (double) l7.get(1) == 14.0;
assert rtt1.isSet("f4");
assert rtt1.isSet("f6");
assert rtt1.isSet("f7");
rtt1.unset(3);
rtt1.unset(5);
rtt1.unset(6);
assert !rtt1.isSet("f4");
assert !rtt1.isSet("f6");
assert !rtt1.isSet("f7");
rtt1.setDouble("f4", -4.0);
rtt1.setList("f6", Arrays.asList(110.0, 120.0));
rtt1.setList("f7", Arrays.asList(130.0, 140.0));
f4 = rtt1.getDouble("f4");
l6 = rtt1.getList("f6");
l7 = rtt1.getList("f7");
assert f4 == -4;
assert (double) l6.get(0) == 110.0 && (double) l6.get(1) == 120.0;
assert (double) l7.get(0) == 130.0 && (double) l7.get(1) == 140.0;
((FWDDataObjectType) rtt1).setForceNormalizedFields(false);
rtt1.set(3, -1.0);
rtt1.set(5, 1.0);
rtt1.set(6, 2.0);
rtt1.set(7, 3.0);
rtt1.set(8, 4.0);
assert (double) rtt1.get(3) == -1;
assert (double) rtt1.get(5) == 1;
assert (double) rtt1.get(6) == 2;
assert (double) rtt1.get(7) == 3;
assert (double) rtt1.get(8) == 4;
assert rtt1.isSet(3);
assert rtt1.isSet(5);
assert rtt1.isSet(6);
assert rtt1.isSet(7);
assert rtt1.isSet(8);
rtt1.unset(3);
rtt1.unset(5);
rtt1.unset(6);
rtt1.unset(7);
rtt1.unset(8);
assert !rtt1.isSet(3);
assert !rtt1.isSet(5);
assert !rtt1.isSet(6);
assert !rtt1.isSet(7);
assert !rtt1.isSet(8);
rtt1.setDouble(3, -2.0);
rtt1.setDouble(5, 10.0);
rtt1.setDouble(6, 20.0);
rtt1.setDouble(7, 30.0);
rtt1.setDouble(8, 40.0);
assert (double) rtt1.getDouble(3) == -2;
assert (double) rtt1.getDouble(5) == 10;
assert (double) rtt1.getDouble(6) == 20;
assert (double) rtt1.getDouble(7) == 30;
assert (double) rtt1.getDouble(8) == 40;
rtt1.set("f4", -3.0);
rtt1.set("f61", 11.0);
rtt1.set("f62", 12.0);
rtt1.set("f71", 13.0);
rtt1.set("f72", 14.0);
assert (double) rtt1.get("f4") == -3;
assert (double) rtt1.get("f61") == 11;
assert (double) rtt1.get("f62") == 12;
assert (double) rtt1.get("f71") == 13;
assert (double) rtt1.get("f72") == 14;
assert rtt1.isSet("f4");
assert rtt1.isSet("f61");
assert rtt1.isSet("f62");
assert rtt1.isSet("f71");
assert rtt1.isSet("f72");
rtt1.unset("f4");
rtt1.unset("f61");
rtt1.unset("f62");
rtt1.unset("f71");
rtt1.unset("f72");
assert !rtt1.isSet("f4");
assert !rtt1.isSet("f61");
assert !rtt1.isSet("f62");
assert !rtt1.isSet("f71");
assert !rtt1.isSet("f72");
rtt1.setDouble("f4", -4.0);
rtt1.setDouble("f61", 110.0);
rtt1.setDouble("f62", 120.0);
rtt1.setDouble("f71", 130.0);
rtt1.setDouble("f72", 140.0);
assert (double) rtt1.getDouble("f4") == -4;
assert (double) rtt1.getDouble("f61") == 110;
assert (double) rtt1.getDouble("f62") == 120;
assert (double) rtt1.getDouble("f71") == 130;
assert (double) rtt1.getDouble("f72") == 140;
rtt1.setInt("F1", 1234);
assert rtt1.getInt("F1") == 1234;
rtt1.setString("f2", "abcd");
assert rtt1.getString("f2").equals("abcd");
rtt1.setBytes("f3", "890890".getBytes());
assert Arrays.equals(rtt1.getBytes(rtt1.getInstanceProperty("f3")), "890890".getBytes());
rtt1.setBigDecimal("f4", new BigDecimal("124124512512512515.125125125"));
rtt1.setBoolean("f5", true);
rtt1.set(5, 1.0);
rtt1.set(6, 2.0);
assert rtt1.getDouble(5) == 1.0;
assert rtt1.getDouble(6) == 2.0;
rtt1.unset(5);
assert !rtt1.isSet(5);
assert rtt1.isSet(6);
rtt1.set("__fwd_denormalized__f61", 4.0);
assert rtt1.getDouble("__FWD_denormalized__F61") == 4.0;
rtt1.set("__fwd_denormalized__f71", 6.0);
rtt1.set("__fWD_denormalized__f72", 7.0);
assert rtt1.getDouble("f71") == 6.0;
assert rtt1.getDouble("F72") == 7.0;
addDataObject(graph, rtt1);
DataObject rtt2 = createDataObject(graph, "tt2");
rtt2.setInt("F1", 1234);
rtt2.setList("f2", Arrays.asList(1.0, 3.0));
rtt2.set("f4", Arrays.asList(1.0, 10.0, 100.0));
assert rtt2.getInt("f1") == 1234;
assert rtt2.getInt(0) == 1234;
assert rtt2.getList(1).equals(Arrays.asList(1.0, 3.0));
assert rtt2.getList("f2").equals(rtt2.getList(1));
assert rtt2.getList("F2").equals(rtt2.getList("f2"));
assert rtt2.getList("F4").equals(Arrays.asList(1.0, 10.0, 100.0));
assert rtt2.getList(3).equals(rtt2.getList("F4"));
assert rtt2.getList(rtt2.getInstanceProperty("F4")).equals(rtt2.getList("F4"));
assert rtt2.isSet("f4");
rtt2.unset("f4");
assert !rtt2.isSet("f4");
addDataObject(graph, rtt2);
System.out.println("Index for table tt1: " + getTableIndex(rtt1));
System.out.println("Index for table tt2: " + getTableIndex(tableTt2));
System.out.println("Index for table tt2: " + getTableIndex(graph, "tt2"));
rtt1 = createDataObject(graph, "tt1");
rtt1.setInt("f1", 1234);
rtt1.setInt("F1", 1234);
rtt1.setString("f2", "abcd");
rtt1.setString("F2", "abcd");
rtt1.setBytes("f3", "890890".getBytes());
rtt1.setBytes("F3", "890890".getBytes());
rtt1.setBigDecimal("f4", new BigDecimal("124124512512512515.125125125"));
rtt1.setBigDecimal("F4", new BigDecimal("124124512512512515.125125125"));
rtt1.setBoolean("f5", true);
rtt1.setBoolean("F5", true);
addDataObject(graph, rtt1);
List<DataObject> rows1 = getDataObjects(graph, "tt1");
Iterator<DataObject> iter1 = rows1.iterator();
while (iter1.hasNext())
{
DataObject row = iter1.next();
List f6 = normalizedExtentForTt1 ? (List) row.get("F6") : null;
if (!normalizedExtentForTt1)
{
row.unset("F62");
//row.unset("f62");
System.out.println(row.isSet("F61") + " " + row.isSet("f62"));
}
System.out.println(getTableName(row));
System.out.println(((FWDDataObjectType) row).primaryKey() + " " +
row.getInt("f1") + " " +
row.getString("f2") + " " +
new String(row.getBytes("f3")) + " " +
row.getBigDecimal("f4") + " " +
row.getBoolean("f5") + " " +
(normalizedExtentForTt1 ? row.getList("f6").get(0) + " " + f6.get(1)
: row.getDouble("F61") + " " + row.getDouble("f62")));
int fieldCount = getFieldCount(row);
for (int i = 0; i < fieldCount; i++)
{
System.out.print(getFieldName(row, i) + " = " + row.get(i) + ";");
}
System.out.println();
}
List<DataObject> rows2 = getDataObjects(graph, 1);
Iterator<DataObject> iter2 = rows2.iterator();
while (iter2.hasNext())
{
DataObject row = iter2.next();
System.out.println(getTableName(row));
System.out.println(((FWDDataObjectType) row).primaryKey() + " " + row.getInt("F1") + " " + row.getDate("f3"));
}
List<DataObject> rrows1 = getChildRows(rtt1, "r1");
for (DataObject row : rrows1)
{
System.out.println(getTableName(row));
System.out.println(((FWDDataObjectType) row).primaryKey() + " " + row.getInt("f1") + " " + row.getDate("f3"));
}
// test the change summary/logging
beginLogging(graph);
// add
DataObject rtt3 = createDataObject(graph, "tt2");
rtt3.setInt("f1", 991234);
rtt3.setDate("F3", new Date());
addDataObject(graph, rtt3);
// delete
rtt2 = getDataObjects(tableTt2).get(0);
rtt2.delete();
// update
rtt1 = getDataObjects(tableTt1).get(0);
rtt1.setInt("f1", 77777);
ChangeSummary summary = graph.getChangeSummary();
System.out.println(summary.isCreated(rtt3));
System.out.println(summary.isDeleted(rtt2));
System.out.println(summary.isModified(rtt1));
endLogging(graph);
System.out.println(summary.isCreated(rtt3));
System.out.println(summary.isDeleted(rtt2));
System.out.println(summary.isModified(rtt1));
List<DataObject> changes = summary.getChangedDataObjects();
for (DataObject d : changes)
{
System.out.println(d.get("f1") + " " +
summary.isCreated(d) + " " +
summary.isDeleted(d) + " " +
summary.isModified(d));
}
System.out.println(changes);
rows2 = getDataObjects(graph, 1);
iter2 = rows2.iterator();
while (iter2.hasNext())
{
DataObject row = iter2.next();
System.out.println(getTableName(row));
System.out.println(((FWDDataObjectType) row).primaryKey() + " " +
row.getInt("f1") + " " + row.getDate("f3"));
}
rows2 = getBeforeObjects(graph, "tt1");
iter2 = rows2.iterator();
while (iter2.hasNext())
{
DataObject row = iter2.next();
System.out.println(getTableName(row));
System.out.println(((FWDDataObjectType) row).primaryKey() + " " + row.getInt("f1"));
}
rows2 = getBeforeObjects(graph, "tt2");
iter2 = rows2.iterator();
while (iter2.hasNext())
{
DataObject row = iter2.next();
System.out.println(getTableName(row));
System.out.println(((FWDDataObjectType) row).primaryKey() + " " +
row.getInt("f1") + " " + row.getDate("f3"));
}
}
}