PropertyDefinition.java
/*
** Module : PropertyDefinition.java
** Abstract : A container for a DMO property's metadata.
**
** Copyright (c) 2013-2022, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
** 001 CA 20130618 Created initial version.
** 002 ECF 20140623 Removed Hibernate dependency. This update simplifies the use of the type
** variable; this is a short-term workaround which breaks some result set
** metadata functionality (not currently in use), but it needs further analysis/work.
** 003 ECF 20140626 Simplified type check.
** 004 SVL 20140709 Added legacyName field.
** 005 CA 20190812 Added toString().
** 006 IAS 20200908 Rework (de)serialization.
** 007 IAS 20200922 Get rid of possible NPE on serialization.
** SVL 20201030 Added format, label and columnLabel fields.
** OM 20201120 Added SCHEMA-MARSHAL implementation.
** CA 20220909 Always marshal the schema, otherwise the remote side will not be able to rebuild the table.
** IAS 20221029 Added support for missed properties.
** IAS 20221102 Re-worked serialization for SCHEMA-MARSHAL == NONE.
*/
/*
** 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.persist;
import java.io.*;
import java.util.*;
import com.goldencode.p2j.persist.orm.*;
import com.goldencode.p2j.util.*;
import static com.goldencode.p2j.persist.AbstractTempTable.*;
import static com.goldencode.util.NativeTypeSerializer.*;
/**
* Container for a DMO property's metadata: name, type and extent. Used to send the metadata of a
* DMO to a remote side, when remote appserver calls are involved.
*
* TODO: add the other schema attributes of a property definition: help, initial, mandatory, validateMessage,
* validationExpression, description, decimals (for decimal type), caseSensitive (for character type)
* codePage, serializeName, xmlNodeName, xmlDataType, etc.
* All these will be serialized only if {@code schemaMarshalLevel == SM_FULL}.
*/
public class PropertyDefinition
implements Externalizable
{
/** Map of primitive names to their classes. */
private static final Map<String, Class<?>> primitiveClasses = new HashMap<>();
/** Default value for no-extent properties. */
private static final int NO_EXTENT = -2;
/** The P2J wrapper type of this property. */
private Class<?> type;
/** The extent of this property, or {@link #NO_EXTENT} if the property has no extent. */
private int extent;
/** The name of this property. */
private String name;
/** The legacy name of this property. */
private String legacyName;
/** The format of this property. */
private String format;
/** The label of this property. */
private String label;
/** The column label of this property. */
private String columnLabel;
/** HELP attribute of this field. */
private String help;
/** INITIAL attribute of this field. */
private String initial;
/** Flag indicating the field should not be included in serialized output. */
private boolean serializeHidden;
/** Flag indicating the field is case-sensitive. */
private boolean caseSensitive;
/** Node type of field in XML output. */
private String xmlNodeType;
/** Code page of the CLOB field. */
private String codePage;
/**
* The SCHEMA-MARSHAL level for the parent temp-table. It is never {@code SM_DEFAULT} when object is to
* be serialized. Always {@code SM_DEFAULT} for a read object.
*/
private int schemaMarshalLevel = SM_DEFAULT;
static
{
primitiveClasses.put("byte", byte.class);
primitiveClasses.put("short", short.class);
primitiveClasses.put("char", char.class);
primitiveClasses.put("int", int.class);
primitiveClasses.put("long", long.class);
primitiveClasses.put("float", float.class);
primitiveClasses.put("double", double.class);
}
/**
* Default c'tor, explicitly added to allow instances of this class to be created on
* deserialization. Not for public use.
*/
public PropertyDefinition()
{
}
/**
* Create a new, no-extent, property definition.
*
* @param name
* The name of this property.
* @param type
* The type of this property.
*
* @throws ClassNotFoundException
* If the given <code>type</code> can not be resolved to a valid class.
*/
public PropertyDefinition(String name, String type)
throws ClassNotFoundException
{
this.name = name;
Class<?> cls = primitiveClasses.containsKey(type) ? primitiveClasses.get(type) : Class.forName(type);
verifyType(cls);
this.type = cls;
this.extent = NO_EXTENT;
}
/**
* Create a new property definition.
*
* @param name
* The name of this property.
* @param type
* The type of this property.
* @param extent
* The extent of this property.
*
* @throws ClassNotFoundException
* If the given {@code type} can not be resolved to a valid class.
*/
public PropertyDefinition(String name, String type, int extent)
throws ClassNotFoundException
{
this(name, type);
this.extent = extent;
}
/**
* Create a new, no-extent, property definition.
*
* @param name
* The name of this property.
* @param type
* A class specifying the type of this property.
*/
public PropertyDefinition(String name, Class<?> type)
{
this(name, type, null);
}
/**
* Create a new, no-extent, property definition.
*
* @param name
* The name of this property.
* @param type
* A class specifying the type of this property.
* @param legacyName
* The legacy name of this property.
*/
public PropertyDefinition(String name, Class<?> type, String legacyName)
{
this(name, type, legacyName, null, null, null);
}
/**
* Create a new property definition.
*
* @param name
* The name of this property.
* @param type
* A class specifying the type of this property.
* @param extent
* The extent of this property.
*/
public PropertyDefinition(String name, Class<?> type, int extent)
{
this(name, type, extent, null);
}
/**
* Create a new property definition.
*
* @param name
* The name of this property.
* @param type
* A class specifying the type of this property.
* @param extent
* The extent of this property.
* @param legacyName
* The legacy name of this property.
*/
public PropertyDefinition(String name, Class<?> type, int extent, String legacyName)
{
this(name, type, extent, legacyName, null, null, null);
}
/**
* Create a new, no-extent, property definition.
*
* @param name
* The name of this property.
* @param type
* A class specifying the type of this property.
* @param legacyName
* The legacy name of this property.
* @param format
* The format of this property.
* @param label
* The label of this property.
* @param columnLabel
* The column label of this property.
*/
public PropertyDefinition(String name,
Class<?> type,
String legacyName,
String format,
String label,
String columnLabel)
{
this(name, type, NO_EXTENT, legacyName, format, label, columnLabel);
}
/**
* Create a new property definition.
*
* @param name
* The name of this property.
* @param type
* A class specifying the type of this property.
* @param legacyName
* The legacy name of this property.
* @param format
* The format of this property.
* @param label
* The label of this property.
* @param columnLabel
* The column label of this property.
*/
public PropertyDefinition(String name,
Class<?> type,
int extent,
String legacyName,
String format,
String label,
String columnLabel)
{
this.name = name;
verifyType(type);
this.type = type;
this.extent = extent;
this.legacyName = legacyName;
this.format = format;
this.label = label;
this.columnLabel = columnLabel;
}
/**
* Create a new property definition.
*
* @param prop
* The ORM field <code>Property</code>.
*/
public PropertyDefinition(Property prop)
{
this.name = prop.name;
verifyType(prop._fwdType);
this.type = prop._fwdType;
int extent = prop.index > 0 ? 0 : prop.extent;
this.extent = extent == 0 ? NO_EXTENT : extent;
this.legacyName = prop.legacy;
this.format = prop.format;
this.label = prop.label;
this.columnLabel = prop.columnLabel;
this.help = prop.help;
this.initial = prop.initial;
this.serializeHidden = prop.serializeHidden;
this.caseSensitive = prop.caseSensitive;
this.xmlNodeType = prop.xmlNodeType;
this.codePage = prop.codePage;
}
/**
* Get the type of this property.
*
* @return See above.
*/
public Class<?> getType()
{
return type;
}
/**
* Get the extent of this property.
*
* @return See above.
*/
public int getExtent()
{
return extent;
}
/**
* Get the name of this property.
*
* @return See above.
*/
public String getName()
{
return name;
}
/**
* Check if this property is an extent property.
*
* @return <code>true</code> if this property is an extent property.
*/
public boolean isExtent()
{
return extent != NO_EXTENT;
}
/**
* Get legacy name of this property.
*
* @return legacy name of this property.
*/
public String getLegacyName()
{
return legacyName;
}
/**
* Get format of this property.
*
* @return format of this property.
*/
public String getFormat()
{
return format;
}
/**
* Get label of this property.
*
* @return label of this property.
*/
public String getLabel()
{
return label;
}
/**
* Get column label of this property.
*
* @return column label of this property.
*/
public String getColumnLabel()
{
return columnLabel;
}
/**
* Get help text of this property.
*
* @return help text of this property.
*/
public String getHelp()
{
return help;
}
/**
* Get initial value of this property.
*
* @return initial value of this property.
*/
public String getInitial()
{
return initial;
}
/**
* Get 'serialize hidden' flag of this property.
*
* @return 'serialize hidden' flag of this property.
*/
public boolean isSerializeHidden()
{
return serializeHidden;
}
/**
* Get 'case-sensitive' flag of this property.
*
* @return 'case-sensitive' flag of this property.
*/
public boolean isCaseSensitive()
{
return caseSensitive;
}
/**
* Get XML node type of this property.
*
* @return XML node type of this property.
*/
public String getXmlNodeType()
{
return xmlNodeType;
}
/**
* Get code page of this property.
*
* @return code page of this property.
*/
public String getCodePage()
{
return codePage;
}
/**
* Send the property definition to the specified output destination.
*
* @param out
* The output destination to which the property definition will be sent.
*
* @throws IOException
* In case of I/O errors.
*/
public final void writeExternal(ObjectOutput out)
throws IOException
{
if (schemaMarshalLevel == SM_NONE)
{
throw new IllegalStateException("Should not be called if schemaMarshalLevel == SM_NONE");
}
boolean full = schemaMarshalLevel == SM_FULL || schemaMarshalLevel == SM_DEFAULT;
writeString(out, name);
writeString(out, legacyName);
writeString(out, type == null ? null : type.getName());
out.writeInt(extent);
writeString(out, initial);
writeString(out, xmlNodeType);
writeString(out, codePage);
int flags = (serializeHidden ? 1 : 0) | (caseSensitive ? 2 : 0) | (full ? 4 : 0);
out.writeByte(flags);
if (!full )
{
return;
}
writeString(out, format);
writeString(out, label);
writeString(out, columnLabel);
writeString(out, help);
}
/**
* Read the property definition from the specified input source.
*
* @param in
* The input source from which the property definition will be read.
*
* @throws IOException
* In case of I/O errors.
*
* @throws ClassNotFoundException
* If the class of property could not be found/loaded.
*/
public final void readExternal(ObjectInput in)
throws IOException,
ClassNotFoundException
{
name = readString(in);
legacyName = readString(in);
String tn = readString(in);
type = (tn == null) ? null : Class.forName(tn);
extent = in.readInt();
initial = readString(in);
xmlNodeType = readString(in);
codePage = readString(in);
int flags = in.readByte();
serializeHidden = (flags & 1) != 0;
caseSensitive = (flags & 2) != 0;
boolean full = (flags & 4) != 0;
if (!full)
{
return;
}
format = readString(in);
label = readString(in);
columnLabel = readString(in);
help = readString(in);
}
/**
* Verify the given type is not null.
*
* @param type
* The type to check.
*
* @throws NullPointerException
* If <code>type</code> is null.
*/
private void verifyType(Class<?> type)
{
if (type == null)
{
throw new NullPointerException("Type may not be null");
}
}
/**
* Get a string representation of this property.
*
* @return See above.
*/
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append(name).append("[legacy: ").append(legacyName).append("]: ").append(type.getSimpleName());
if (extent != NO_EXTENT && extent != 0)
{
sb.append("[").append(extent).append("]");
}
return sb.toString();
}
/**
* Configures the SCHEMA-MARSHAL level used for serialization of the property.
*
* @param schemaMarshalLevel
* The new SCHEMA-MARSHAL level. See {@link AbstractTempTable} static constants for details.
*/
public void setMarshalLevel(int schemaMarshalLevel)
{
this.schemaMarshalLevel = schemaMarshalLevel;
}
}