From 14f10ed3c3e1b57cb0ee54c6f132dd8d18ab0c4f Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Thu, 1 Feb 2024 12:48:01 +0100 Subject: [PATCH] trying to fix the issue where empty value fields are supposed to be nulled out to avoid type matching issues. --- .../ortus/extension/orm/HibernateCaster.java | 1352 ++++++------- .../orm/event/EventListenerIntegrator.java | 772 ++++---- .../extension/orm/mapping/CFConstants.java | 446 +++-- .../ortus/extension/orm/util/CommonUtil.java | 1745 +++++++++-------- 4 files changed, 2189 insertions(+), 2126 deletions(-) diff --git a/extension/src/main/java/ortus/extension/orm/HibernateCaster.java b/extension/src/main/java/ortus/extension/orm/HibernateCaster.java index 1c9d5229..d06f3b6e 100755 --- a/extension/src/main/java/ortus/extension/orm/HibernateCaster.java +++ b/extension/src/main/java/ortus/extension/orm/HibernateCaster.java @@ -13,11 +13,6 @@ import org.hibernate.metadata.ClassMetadata; import org.hibernate.type.Type; -import ortus.extension.orm.mapping.CFConstants; -import ortus.extension.orm.util.CommonUtil; -import ortus.extension.orm.util.ExceptionUtil; -import ortus.extension.orm.util.HibernateUtil; - import lucee.commons.lang.types.RefBoolean; import lucee.loader.engine.CFMLEngineFactory; import lucee.loader.util.Util; @@ -32,670 +27,691 @@ import lucee.runtime.type.Collection.Key; import lucee.runtime.type.Query; import lucee.runtime.type.Struct; +import ortus.extension.orm.mapping.CFConstants; +import ortus.extension.orm.util.CommonUtil; +import ortus.extension.orm.util.ExceptionUtil; +import ortus.extension.orm.util.HibernateUtil; public class HibernateCaster { - private HibernateCaster() { - throw new IllegalStateException( "Utility class; please don't instantiate!" ); - } - - private static final int NULL = -178696; - - /** - * Convert a Java object (such as a Lucee Collection or java List) to a CFML-compatible value. - * - * @param src Java object. - * @return CFML-compatible List object or Collection object or the original unmodified value. - */ - @SuppressWarnings("rawtypes") - public static Object toCFML( Object src ) { - if ( src == null ) - return null; - if ( src instanceof Collection ) - return src; - - if ( src instanceof List ) { - return toCFML( ( List ) src ); - } - return src; - } - - /** - * Convert a Java list to a CFML list. - * - * @param src Java List of objects. - * @return CFML-compatible List object. - */ - @SuppressWarnings("rawtypes") - public static Array toCFML( List src ) { - int size = src.size(); - - Array trg = CommonUtil.createArray(); - for ( int i = 0; i < size; i++ ) { - trg.setEL( i + 1, toCFML( src.get( i ) ) ); - } - return trg; - } - - /** - * Get the persistent entity name for the given Component. - * - * Entity name is either derived from the `entityname` attribute, or the component name itself. - * - * @param cfc A persistent Component. - * @return - */ - public static String getEntityName( Component cfc ) { - - String name = null; - try { - name = CommonUtil.toString( cfc.getMetaStructItem( CommonUtil.ENTITY_NAME ), null ); - } catch ( Exception t ) { - try { - Struct md = cfc.getMetaData( CommonUtil.pc() ); - name = CommonUtil.toString( md.get( CommonUtil.ENTITY_NAME ), null ); - - } catch ( PageException e ) { - // @TODO: @nextMajorRelease either drop this catch and let errors out, or handle it properly with a log and fall back. - } - } - - if ( !Util.isEmpty( name ) ) { - return name; - } - return getName( cfc ); - - } - - /** - * Get the component name from the file path. - * - * @param cfc A Lucee component. - * @return Last item in the dot-notation component path - */ - private static String getName( Component cfc ) { - // @TODO: cfc.getName() should return the real case, this should not be needed - String name = CommonUtil.last( cfc.getPageSource().getDisplayPath(), "\\/" ); - int index = name.lastIndexOf( '.' ); - return name.substring( 0, index ); - } - - /** - * Try to retrieve a Hibernate type for this column by checking both the {@link ColumnInfo} or the string sql type, - * whichever is populated and returns a result first. - * - * @param info a {@link ortus.extension.orm.ColumnInfo} object containing a known Java SQL type. - * @param type The string name of a SQL type. - * @param defaultValue Default to return if SQL type not detected. - * @return - */ - public static String toHibernateType( ColumnInfo info, String type, String defaultValue ) { - - // no type defined - if ( Util.isEmpty( type, true ) ) { - return HibernateCaster.toHibernateType( info, defaultValue ); - } - - // type defined - String tmp = HibernateCaster.toHibernateType( type, null ); - if ( tmp != null ) - return tmp; - - if ( info != null ) { - tmp = HibernateCaster.toHibernateType( info, defaultValue ); - return tmp; - } - return defaultValue; - - } - - private static int toSQLType( String type, int defaultValue ) { - type = type.trim().toLowerCase(); - type = toHibernateType( type, type ); - if ( "long".equals( type ) ) - return Types.BIGINT; - if ( "binary".equals( type ) ) - return Types.BINARY; - if ( "boolean".equals( type ) ) - return Types.BIT; - if ( "blob".equals( type ) ) - return Types.BLOB; - if ( "character".equals( type ) ) - return Types.CHAR; - if ( "clob".equals( type ) ) - return Types.CLOB; - if ( "date".equals( type ) ) - return Types.DATE; - if ( "big_decimal".equals( type ) ) - return Types.DECIMAL; - if ( "big_integer".equals( type ) ) - return Types.NUMERIC; - if ( "double".equals( type ) ) - return Types.DOUBLE; - if ( "float".equals( type ) ) - return Types.FLOAT; - if ( "integer".equals( type ) ) - return Types.INTEGER; - if ( "string".equals( type ) ) - return Types.VARCHAR; - if ( "short".equals( type ) ) - return Types.SMALLINT; - if ( "time".equals( type ) ) - return Types.TIME; - if ( "timestamp".equals( type ) ) - return Types.TIMESTAMP; - if ( "byte".equals( type ) ) - return Types.TINYINT; - - return defaultValue; - } - - /** - * Translate the given `java.sql.Types` SQL type from an {@link ColumnInfo} to its Hibernate counterpart. - * - * Might be utilized after pulling ORM mapping data from an existing database layout. - * - * @param info a {@link ortus.extension.orm.ColumnInfo} object containing a known Java SQL type. - * @param defaultValue Default to return if SQL type not detected. - */ - private static String toHibernateType( ColumnInfo info, String defaultValue ) { - if ( info == null ) - return defaultValue; - - String rtn = toHibernateType( info.getType(), null ); - if ( rtn != null ) - return rtn; - return toHibernateType( info.getTypeName(), defaultValue ); - } - - /** - * Translate the given `java.sql.Types` SQL type for a column or field to its Hibernate counterpart. - * - * @param type One of the SQL types. {@link java.sql.Types} - * @param defaultValue Default to return if SQL type not detected. - */ - private static String toHibernateType( int type, String defaultValue ) { - switch ( type ) { - case Types.ARRAY : - return ""; - case Types.BIGINT : - return "long"; - case Types.BINARY : - return "binary"; - case Types.BIT : - return "boolean"; - case Types.BLOB : - return "blob"; - case Types.BOOLEAN : - return "boolean"; - case Types.CHAR : - return "string"; - case Types.CLOB : - return "clob"; - case Types.DATE : - return "date"; - case Types.DECIMAL : - return "big_decimal"; - case Types.DOUBLE : - return "double"; - case Types.FLOAT : - return "float"; - case Types.INTEGER : - return "integer"; - case Types.LONGVARBINARY : - return "binary"; - case Types.LONGVARCHAR : - return "string"; - case Types.NUMERIC : - return "big_decimal"; - case Types.SMALLINT : - return "short"; - case Types.TIME : - return "time"; - case Types.TIMESTAMP : - return "timestamp"; - case Types.TINYINT : - return "byte"; - case Types.VARBINARY : - return "binary"; - case Types.NVARCHAR : - return "string"; - case Types.VARCHAR : - return "string"; - default: - return defaultValue; - } - } - - // calendar_date: A type mapping for a Calendar object that represents a date - // calendar: A type mapping for a Calendar object that represents a datetime. - private static String toHibernateType( String type, String defaultValue ) { - type = type.trim().toLowerCase(); - type = type.replace( "java.lang.", "" ); - type = type.replace( "java.util.", "" ); - type = type.replace( "java.sql.", "" ); - - // return same value - if ( "long".equals( type ) ) - return type; - if ( "binary".equals( type ) ) - return type; - if ( "boolean".equals( type ) ) - return type; - if ( "blob".equals( type ) ) - return "binary"; - if ( "character".equals( type ) ) - return type; - if ( "clob".equals( type ) ) - return "text"; - if ( "date".equals( type ) ) - return type; - if ( "big_decimal".equals( type ) ) - return type; - if ( "double".equals( type ) ) - return type; - if ( "float".equals( type ) ) - return type; - if ( "integer".equals( type ) ) - return type; - if ( "string".equals( type ) ) - return type; - if ( "big_integer".equals( type ) ) - return type; - if ( "short".equals( type ) ) - return type; - if ( "time".equals( type ) ) - return type; - if ( "timestamp".equals( type ) ) - return type; - if ( "byte".equals( type ) ) - return type; - if ( "string".equals( type ) ) - return type; - if ( "text".equals( type ) ) - return type; - if ( "calendar".equals( type ) ) - return type; - if ( "calendar_date".equals( type ) ) - return type; - if ( "locale".equals( type ) ) - return type; - if ( "timezone".equals( type ) ) - return type; - if ( "currency".equals( type ) ) - return type; - - if ( "imm_date".equals( type ) ) - return type; - if ( "imm_time".equals( type ) ) - return type; - if ( "imm_timestamp".equals( type ) ) - return type; - if ( "imm_calendar".equals( type ) ) - return type; - if ( "imm_calendar_date".equals( type ) ) - return type; - if ( "imm_serializable".equals( type ) ) - return type; - if ( "imm_binary".equals( type ) ) - return type; - - // return different value - if ( "bigint".equals( type ) ) - return "long"; - if ( "bit".equals( type ) ) - return "boolean"; - - if ( "int".equals( type ) ) - return "integer"; - if ( "char".equals( type ) ) - return "character"; - - if ( "bool".equals( type ) ) - return "boolean"; - if ( "yes-no".equals( type ) ) - return "yes_no"; - if ( "yesno".equals( type ) ) - return "yes_no"; - if ( "yes_no".equals( type ) ) - return "yes_no"; - if ( "true-false".equals( type ) ) - return "true_false"; - if ( "truefalse".equals( type ) ) - return "true_false"; - if ( "true_false".equals( type ) ) - return "true_false"; - if ( "varchar".equals( type ) ) - return "string"; - if ( "big-decimal".equals( type ) ) - return "big_decimal"; - if ( "bigdecimal".equals( type ) ) - return "big_decimal"; - if ( "java.math.bigdecimal".equals( type ) ) - return "big_decimal"; - if ( "big-integer".equals( type ) ) - return "big_integer"; - if ( "biginteger".equals( type ) ) - return "big_integer"; - if ( "bigint".equals( type ) ) - return "big_integer"; - if ( "java.math.biginteger".equals( type ) ) - return "big_integer"; - if ( "byte[]".equals( type ) ) - return "binary"; - if ( "serializable".equals( type ) ) - return "serializable"; - - if ( "datetime".equals( type ) ) - return "timestamp"; - if ( "numeric".equals( type ) ) - return "double"; - if ( "number".equals( type ) ) - return "double"; - if ( "numeric".equals( type ) ) - return "double"; - if ( "char".equals( type ) ) - return "character"; - if ( "nchar".equals( type ) ) - return "character"; - if ( "decimal".equals( type ) ) - return "double"; - if ( "eurodate".equals( type ) ) - return "timestamp"; - if ( "usdate".equals( type ) ) - return "timestamp"; - if ( "int".equals( type ) ) - return "integer"; - if ( "varchar".equals( type ) ) - return "string"; - if ( "nvarchar".equals( type ) ) - return "string"; - - return defaultValue; - } - - public static Object toHibernateValue( PageContext pc, Object value, String type ) throws PageException { - type = toHibernateType( type, null ); - // return same value - if ( "long".equals( type ) ) - return CommonUtil.toLong( value ); - if ( "binary".equals( type ) || "imm_binary".equals( type ) ) - return CommonUtil.toBinary( value ); - if ( "boolean".equals( type ) || "yes_no".equals( type ) || "true_false".equals( type ) ) - return CommonUtil.toBoolean( value ); - if ( "character".equals( type ) ) - return CommonUtil.toCharacter( value ); - if ( "date".equals( type ) || "imm_date".equals( type ) ) - return CommonUtil.toDate( value, pc.getTimeZone() ); - if ( "big_decimal".equals( type ) ) - return CommonUtil.toBigDecimal( value ); - if ( "double".equals( type ) ) - return CommonUtil.toDouble( value ); - if ( "float".equals( type ) ) - return CommonUtil.toFloat( value ); - if ( "integer".equals( type ) ) - return CommonUtil.toInteger( value ); - if ( "string".equals( type ) ) - return CommonUtil.toString( value ); - if ( "big_integer".equals( type ) ) - return new BigInteger( CommonUtil.toString( value ) ); - if ( "short".equals( type ) ) - return CommonUtil.toShort( value ); - if ( "time".equals( type ) || "imm_time".equals( type ) ) - return new Time( CommonUtil.toDate( value, pc.getTimeZone() ).getTime() ); - if ( "timestamp".equals( type ) || "imm_timestamp".equals( type ) ) - return new Timestamp( CommonUtil.toDate( value, pc.getTimeZone() ).getTime() ); - if ( "byte".equals( type ) ) - return CommonUtil.toBinary( value ); - if ( "text".equals( type ) ) - return CommonUtil.toString( value ); - if ( "calendar".equals( type ) || "calendar_date".equals( type ) || "imm_calendar".equals( type ) - || "imm_calendar_date".equals( type ) ) - return CommonUtil.toCalendar( CommonUtil.toDate( value, pc.getTimeZone() ), pc.getTimeZone(), pc.getLocale() ); - if ( "locale".equals( type ) ) - return CommonUtil.toLocale( CommonUtil.toString( value ) ); - if ( "timezone".equals( type ) ) - return CommonUtil.toTimeZone( value, null ); - if ( "currency".equals( type ) ) - return value; - - if ( "imm_serializable".equals( type ) ) - return value; - if ( "serializable".equals( type ) ) - return "serializable"; - - return value; - } - - /** - * Given the Property object (representing a property on a Lucee .cfc Component file), return the value of the - * property in the correct type as specified by the property's `ormtype` or `type` annotations. - * - * @param entity - * The Lucee component to pull the value from. (Since Lucee's Property.getValue() method returns the - * default, NOT the actual value, we need to retrieve the value from the ComponentScope object.) - * @param property - * A persistent Property from a persistent component. - * - * @return The property value with the correct Hibernate typing. {@link toHibernateValue(PageContext pc, Object - * value, String type)} - * - * @throws PageException - */ - public static Object toHibernateValue( Component entity, Property property ) throws PageException { - PageContext pc = CFMLEngineFactory.getInstance().getThreadPageContext(); - Object value = entity.getComponentScope().get( CommonUtil.createKey( property.getName() ), property.getDefault() ); - Struct meta = ( Struct ) property.getMetaData(); - String ormType = CommonUtil.toString( meta.get( CommonUtil.createKey( "ormtype" ), "" ) ); - String fieldType = !ormType.trim().isEmpty() ? ormType : property.getType(); - return toHibernateValue( pc, value, fieldType ); - } - - /** - * translate CFMl specific types to Hibernate/SQL specific types - * - * @param type - * @param value - * @param isArray - * - * @throws PageException - */ - public static Object toSQL( Type type, Object value, RefBoolean isArray ) throws PageException { - int t = toSQLType( type.getName(), Types.OTHER ); - return toSQL( t, value, isArray ); - } - - /** - * translate CFMl specific type to SQL specific types - * - * @param sqlType - * @param value - * @param isArray - * - * @throws PageException - */ - private static Object toSQL( int sqlType, Object value, RefBoolean isArray ) throws PageException { - if ( sqlType == Types.OTHER && value instanceof PersistentCollection ) { - return value; - } - - if ( isArray != null ) - isArray.setValue( false ); - - Boolean _isArray = null; - boolean hasType = sqlType != Types.OTHER; - - // first we try to convert without any checking - if ( hasType ) { - try { - return CommonUtil.toSqlType( CommonUtil.toSQLItem( value, sqlType ) ); - } catch ( PageException pe ) { - _isArray = CommonUtil.isArray( value ); - if ( !_isArray.booleanValue() ) - throw pe; - } - } - - // already a hibernate type - - // can only be null if type is other - if ( _isArray == null && !CommonUtil.isArray( value )) { - return value; - } - - // at this point it is for sure that the value is an array - if ( isArray != null ) - isArray.setValue( true ); - Array src = CommonUtil.toArray( value ); - Iterator it = src.valueIterator(); - ArrayList trg = new ArrayList<>(); - Object v; - while ( it.hasNext() ) { - v = it.next(); - if ( v == null ) - continue; - if ( hasType ) { - trg.add( CommonUtil.toSqlType( CommonUtil.toSQLItem( v, sqlType ) ) ); - } else - trg.add( v ); - } - return trg; - } - - public static lucee.runtime.type.Query toQuery( PageContext pc, HibernateORMSession session, Object obj, String name ) - throws PageException { - Query qry = null; - // a single entity - if ( !CommonUtil.isArray( obj ) ) { - qry = toQuery( pc, session, HibernateCaster.toComponent( obj ), name, null, 1, 1 ); - } - - // a array of entities - else { - Array arr = CommonUtil.toArray( obj ); - int len = arr.size(); - if ( len > 0 ) { - Iterator it = arr.valueIterator(); - int row = 1; - while ( it.hasNext() ) { - qry = toQuery( pc, session, HibernateCaster.toComponent( it.next() ), name, qry, len, row++ ); - } - } else - qry = CommonUtil.createQuery( new Collection.Key[ 0 ], 0, "orm" ); - } - - if ( qry == null ) { - if ( !Util.isEmpty( name ) ) { - String message = String.format( "there is no entity inheritance that match the name [%s]", name ); - throw ExceptionUtil.createException( session, null, message, null ); - } else { - throw ExceptionUtil.createException( session, null, "cannot create query", null ); - } - } - return qry; - } - - private static Query toQuery( PageContext pc, HibernateORMSession session, Component cfc, String entityName, Query qry, - int rowcount, int row ) throws PageException { - // inheritance mapping - if ( !Util.isEmpty( entityName ) ) { - return inheritance( pc, session, cfc, qry, entityName ); - } - return populateQuery( pc, session, cfc, qry ); - } - - private static Query populateQuery( PageContext pc, HibernateORMSession session, Component cfc, Query qry ) - throws PageException { - Property[] properties = CommonUtil.getProperties( cfc, true, true, false, false ); - String dsn = CommonUtil.getDataSourceName( pc, cfc ); - ComponentScope scope = cfc.getComponentScope(); - HibernateORMEngine engine = ( HibernateORMEngine ) session.getEngine(); - - // init - if ( qry == null ) { - SessionFactory factory = session.getRawSessionFactory( dsn ); - ClassMetadata md = factory.getClassMetadata( getEntityName( cfc ) ); - Array names = CommonUtil.createArray(); - Array types = CommonUtil.createArray(); - String name; - int t; - Object obj; - Struct sct; - String fieldType; - for ( int i = 0; i < properties.length; i++ ) { - obj = properties[ i ].getMetaData(); - if ( obj instanceof Struct ) { - sct = ( Struct ) obj; - fieldType = CommonUtil.toString( sct.get( CommonUtil.FIELDTYPE, null ), null ); - if ( CFConstants.Relationships.isRelationshipType(fieldType)){ - continue; - } - - } - - name = HibernateUtil.validateColumnName( md, properties[ i ].getName(), null ); - names.append( name ); - if ( name != null ) { - - t = HibernateCaster.toSQLType( HibernateUtil.getPropertyType( md, name ).getName(), NULL ); - if ( t == NULL ) - types.append( "object" ); - else - types.append( CFMLEngineFactory.getInstance().getDBUtil().toStringType( t ) ); - } else - types.append( "object" ); - } - - qry = CommonUtil.createQuery( names, types, 0, getEntityName( cfc ) ); - - } - // check - else if ( engine.getMode() == ORMEngine.MODE_STRICT && !qry.getName().equals( getEntityName( cfc ) ) ) { - throw ExceptionUtil.createException( session, null, "can only merge entities of the same kind to a query", null ); - } - - // populate - Key[] names = CFMLEngineFactory.getInstance().getDBUtil().getColumnNames( qry ); - - int row = qry.addRow(); - for ( int i = 0; i < names.length; i++ ) { - qry.setAtEL( names[ i ], row, scope.get( names[ i ], null ) ); - } - return qry; - } - - private static Query inheritance( PageContext pc, HibernateORMSession session, Component cfc, Query qry, String entityName ) - throws PageException { - Property[] properties = cfc.getProperties( true, false, false, false ); - ComponentScope scope = cfc.getComponentScope(); - Object value; - Array arr; - for ( int i = 0; i < properties.length; i++ ) { - value = scope.get( CommonUtil.createKey( properties[ i ].getName() ), null ); - if ( value instanceof Component ) { - qry = inheritance( pc, session, qry, cfc, ( Component ) value, entityName ); - } else if ( CommonUtil.isArray( value ) ) { - arr = CommonUtil.toArray( value ); - Iterator it = arr.valueIterator(); - while ( it.hasNext() ) { - value = it.next(); - if ( value instanceof Component ) { - qry = inheritance( pc, session, qry, cfc, ( Component ) value, entityName ); - } - } - } - } - return qry; - } - - private static Query inheritance( PageContext pc, HibernateORMSession session, Query qry, Component parent, Component child, - String entityName ) throws PageException { - if ( getEntityName( child ).equalsIgnoreCase( entityName ) ) - return populateQuery( pc, session, child, qry ); - return inheritance( pc, session, child, qry, entityName );// @TODO: geh ACF auch so tief? - } - - public static Component toComponent( Object obj ) throws PageException { - return CommonUtil.toComponent( obj ); - } + private HibernateCaster() { + throw new IllegalStateException( "Utility class; please don't instantiate!" ); + } + + private static final int NULL = -178696; + + /** + * Convert a Java object (such as a Lucee Collection or java List) to a CFML-compatible value. + * + * @param src Java object. + * + * @return CFML-compatible List object or Collection object or the original unmodified value. + */ + @SuppressWarnings( "rawtypes" ) + public static Object toCFML( Object src ) { + if ( src == null ) + return null; + if ( src instanceof Collection ) + return src; + + if ( src instanceof List ) { + return toCFML( ( List ) src ); + } + return src; + } + + /** + * Convert a Java list to a CFML list. + * + * @param src Java List of objects. + * + * @return CFML-compatible List object. + */ + @SuppressWarnings( "rawtypes" ) + public static Array toCFML( List src ) { + int size = src.size(); + + Array trg = CommonUtil.createArray(); + for ( int i = 0; i < size; i++ ) { + trg.setEL( i + 1, toCFML( src.get( i ) ) ); + } + return trg; + } + + /** + * Get the persistent entity name for the given Component. + * + * Entity name is either derived from the `entityname` attribute, or the component name itself. + * + * @param cfc A persistent Component. + * + * @return + */ + public static String getEntityName( Component cfc ) { + + String name = null; + try { + name = CommonUtil.toString( cfc.getMetaStructItem( CommonUtil.ENTITY_NAME ), null ); + } catch ( Exception t ) { + try { + Struct md = cfc.getMetaData( CommonUtil.pc() ); + name = CommonUtil.toString( md.get( CommonUtil.ENTITY_NAME ), null ); + + } catch ( PageException e ) { + // @TODO: @nextMajorRelease either drop this catch and let errors out, or handle it properly with a log and fall back. + } + } + + if ( !Util.isEmpty( name ) ) { + return name; + } + return getName( cfc ); + + } + + /** + * Get the component name from the file path. + * + * @param cfc A Lucee component. + * + * @return Last item in the dot-notation component path + */ + private static String getName( Component cfc ) { + // @TODO: cfc.getName() should return the real case, this should not be needed + String name = CommonUtil.last( cfc.getPageSource().getDisplayPath(), "\\/" ); + int index = name.lastIndexOf( '.' ); + return name.substring( 0, index ); + } + + /** + * Try to retrieve a Hibernate type for this column by checking both the {@link ColumnInfo} or the string sql type, + * whichever is populated and returns a result first. + * + * @param info a {@link ortus.extension.orm.ColumnInfo} object containing a known Java SQL type. + * @param type The string name of a SQL type. + * @param defaultValue Default to return if SQL type not detected. + * + * @return + */ + public static String toHibernateType( ColumnInfo info, String type, String defaultValue ) { + + // no type defined + if ( Util.isEmpty( type, true ) ) { + return HibernateCaster.toHibernateType( info, defaultValue ); + } + + // type defined + String tmp = HibernateCaster.toHibernateType( type, null ); + if ( tmp != null ) + return tmp; + + if ( info != null ) { + tmp = HibernateCaster.toHibernateType( info, defaultValue ); + return tmp; + } + return defaultValue; + + } + + private static int toSQLType( String type, int defaultValue ) { + type = type.trim().toLowerCase(); + type = toHibernateType( type, type ); + if ( "long".equals( type ) ) + return Types.BIGINT; + if ( "binary".equals( type ) ) + return Types.BINARY; + if ( "boolean".equals( type ) ) + return Types.BIT; + if ( "blob".equals( type ) ) + return Types.BLOB; + if ( "character".equals( type ) ) + return Types.CHAR; + if ( "clob".equals( type ) ) + return Types.CLOB; + if ( "date".equals( type ) ) + return Types.DATE; + if ( "big_decimal".equals( type ) ) + return Types.DECIMAL; + if ( "big_integer".equals( type ) ) + return Types.NUMERIC; + if ( "double".equals( type ) ) + return Types.DOUBLE; + if ( "float".equals( type ) ) + return Types.FLOAT; + if ( "integer".equals( type ) ) + return Types.INTEGER; + if ( "string".equals( type ) ) + return Types.VARCHAR; + if ( "short".equals( type ) ) + return Types.SMALLINT; + if ( "time".equals( type ) ) + return Types.TIME; + if ( "timestamp".equals( type ) ) + return Types.TIMESTAMP; + if ( "byte".equals( type ) ) + return Types.TINYINT; + + return defaultValue; + } + + /** + * Translate the given `java.sql.Types` SQL type from an {@link ColumnInfo} to its Hibernate counterpart. + * + * Might be utilized after pulling ORM mapping data from an existing database layout. + * + * @param info a {@link ortus.extension.orm.ColumnInfo} object containing a known Java SQL type. + * @param defaultValue Default to return if SQL type not detected. + */ + private static String toHibernateType( ColumnInfo info, String defaultValue ) { + if ( info == null ) + return defaultValue; + + String rtn = toHibernateType( info.getType(), null ); + if ( rtn != null ) + return rtn; + return toHibernateType( info.getTypeName(), defaultValue ); + } + + /** + * Translate the given `java.sql.Types` SQL type for a column or field to its Hibernate counterpart. + * + * @param type One of the SQL types. {@link java.sql.Types} + * @param defaultValue Default to return if SQL type not detected. + */ + private static String toHibernateType( int type, String defaultValue ) { + switch ( type ) { + case Types.ARRAY : + return ""; + case Types.BIGINT : + return "long"; + case Types.BINARY : + return "binary"; + case Types.BIT : + return "boolean"; + case Types.BLOB : + return "blob"; + case Types.BOOLEAN : + return "boolean"; + case Types.CHAR : + return "string"; + case Types.CLOB : + return "clob"; + case Types.DATE : + return "date"; + case Types.DECIMAL : + return "big_decimal"; + case Types.DOUBLE : + return "double"; + case Types.FLOAT : + return "float"; + case Types.INTEGER : + return "integer"; + case Types.LONGVARBINARY : + return "binary"; + case Types.LONGVARCHAR : + return "string"; + case Types.NUMERIC : + return "big_decimal"; + case Types.SMALLINT : + return "short"; + case Types.TIME : + return "time"; + case Types.TIMESTAMP : + return "timestamp"; + case Types.TINYINT : + return "byte"; + case Types.VARBINARY : + return "binary"; + case Types.NVARCHAR : + return "string"; + case Types.VARCHAR : + return "string"; + default : + return defaultValue; + } + } + + // calendar_date: A type mapping for a Calendar object that represents a date + // calendar: A type mapping for a Calendar object that represents a datetime. + private static String toHibernateType( String type, String defaultValue ) { + type = type.trim().toLowerCase(); + type = type.replace( "java.lang.", "" ); + type = type.replace( "java.util.", "" ); + type = type.replace( "java.sql.", "" ); + + // return same value + if ( "long".equals( type ) ) + return type; + if ( "binary".equals( type ) ) + return type; + if ( "boolean".equals( type ) ) + return type; + if ( "blob".equals( type ) ) + return "binary"; + if ( "character".equals( type ) ) + return type; + if ( "clob".equals( type ) ) + return "text"; + if ( "date".equals( type ) ) + return type; + if ( "big_decimal".equals( type ) ) + return type; + if ( "double".equals( type ) ) + return type; + if ( "float".equals( type ) ) + return type; + if ( "integer".equals( type ) ) + return type; + if ( "string".equals( type ) ) + return type; + if ( "big_integer".equals( type ) ) + return type; + if ( "short".equals( type ) ) + return type; + if ( "time".equals( type ) ) + return type; + if ( "timestamp".equals( type ) ) + return type; + if ( "byte".equals( type ) ) + return type; + if ( "string".equals( type ) ) + return type; + if ( "text".equals( type ) ) + return type; + if ( "calendar".equals( type ) ) + return type; + if ( "calendar_date".equals( type ) ) + return type; + if ( "locale".equals( type ) ) + return type; + if ( "timezone".equals( type ) ) + return type; + if ( "currency".equals( type ) ) + return type; + + if ( "imm_date".equals( type ) ) + return type; + if ( "imm_time".equals( type ) ) + return type; + if ( "imm_timestamp".equals( type ) ) + return type; + if ( "imm_calendar".equals( type ) ) + return type; + if ( "imm_calendar_date".equals( type ) ) + return type; + if ( "imm_serializable".equals( type ) ) + return type; + if ( "imm_binary".equals( type ) ) + return type; + + // return different value + if ( "bigint".equals( type ) ) + return "long"; + if ( "bit".equals( type ) ) + return "boolean"; + + if ( "int".equals( type ) ) + return "integer"; + if ( "char".equals( type ) ) + return "character"; + + if ( "bool".equals( type ) ) + return "boolean"; + if ( "yes-no".equals( type ) ) + return "yes_no"; + if ( "yesno".equals( type ) ) + return "yes_no"; + if ( "yes_no".equals( type ) ) + return "yes_no"; + if ( "true-false".equals( type ) ) + return "true_false"; + if ( "truefalse".equals( type ) ) + return "true_false"; + if ( "true_false".equals( type ) ) + return "true_false"; + if ( "varchar".equals( type ) ) + return "string"; + if ( "big-decimal".equals( type ) ) + return "big_decimal"; + if ( "bigdecimal".equals( type ) ) + return "big_decimal"; + if ( "java.math.bigdecimal".equals( type ) ) + return "big_decimal"; + if ( "big-integer".equals( type ) ) + return "big_integer"; + if ( "biginteger".equals( type ) ) + return "big_integer"; + if ( "bigint".equals( type ) ) + return "big_integer"; + if ( "java.math.biginteger".equals( type ) ) + return "big_integer"; + if ( "byte[]".equals( type ) ) + return "binary"; + if ( "serializable".equals( type ) ) + return "serializable"; + + if ( "datetime".equals( type ) ) + return "timestamp"; + if ( "numeric".equals( type ) ) + return "double"; + if ( "number".equals( type ) ) + return "double"; + if ( "numeric".equals( type ) ) + return "double"; + if ( "char".equals( type ) ) + return "character"; + if ( "nchar".equals( type ) ) + return "character"; + if ( "decimal".equals( type ) ) + return "double"; + if ( "eurodate".equals( type ) ) + return "timestamp"; + if ( "usdate".equals( type ) ) + return "timestamp"; + if ( "int".equals( type ) ) + return "integer"; + if ( "varchar".equals( type ) ) + return "string"; + if ( "nvarchar".equals( type ) ) + return "string"; + + return defaultValue; + } + + public static Object toHibernateValue( PageContext pc, Object value, String type ) throws PageException { + type = toHibernateType( type, null ); + // return same value + if ( "long".equals( type ) ) + return CommonUtil.toLong( value ); + if ( "binary".equals( type ) || "imm_binary".equals( type ) ) + return CommonUtil.toBinary( value ); + if ( "boolean".equals( type ) || "yes_no".equals( type ) || "true_false".equals( type ) ) + return CommonUtil.toBoolean( value ); + if ( "character".equals( type ) ) + return CommonUtil.toCharacter( value ); + if ( "date".equals( type ) || "imm_date".equals( type ) ) + return CommonUtil.toDate( value, pc.getTimeZone() ); + if ( "big_decimal".equals( type ) ) + return CommonUtil.toBigDecimal( value ); + if ( "double".equals( type ) ) + return CommonUtil.toDouble( value ); + if ( "float".equals( type ) ) + return CommonUtil.toFloat( value ); + if ( "integer".equals( type ) ) + return CommonUtil.toInteger( value ); + if ( "string".equals( type ) ) + return CommonUtil.toString( value ); + if ( "big_integer".equals( type ) ) + return new BigInteger( CommonUtil.toString( value ) ); + if ( "short".equals( type ) ) + return CommonUtil.toShort( value ); + if ( "time".equals( type ) || "imm_time".equals( type ) ) + return new Time( CommonUtil.toDate( value, pc.getTimeZone() ).getTime() ); + if ( "timestamp".equals( type ) || "imm_timestamp".equals( type ) ) + return new Timestamp( CommonUtil.toDate( value, pc.getTimeZone() ).getTime() ); + if ( "byte".equals( type ) ) + return CommonUtil.toBinary( value ); + if ( "text".equals( type ) ) + return CommonUtil.toString( value ); + if ( "calendar".equals( type ) || "calendar_date".equals( type ) || "imm_calendar".equals( type ) + || "imm_calendar_date".equals( type ) ) + return CommonUtil.toCalendar( CommonUtil.toDate( value, pc.getTimeZone() ), pc.getTimeZone(), pc.getLocale() ); + if ( "locale".equals( type ) ) + return CommonUtil.toLocale( CommonUtil.toString( value ) ); + if ( "timezone".equals( type ) ) + return CommonUtil.toTimeZone( value, null ); + if ( "currency".equals( type ) ) + return value; + + if ( "imm_serializable".equals( type ) ) + return value; + if ( "serializable".equals( type ) ) + return "serializable"; + + return value; + } + + /** + * Given the Property object (representing a property on a Lucee .cfc Component file), return the value of the + * property in the correct type as specified by the property's `ormtype` or `type` annotations. + * + * @param entity The Lucee component to pull the value from. (Since Lucee's Property.getValue() method returns the + * default, NOT the actual value, we need to retrieve the value from the ComponentScope object.) + * @param property A persistent Property from a persistent component. + * + * @return The property value with the correct Hibernate typing. {@link toHibernateValue(PageContext pc, Object + * value, String type)} + * + * @throws PageException + */ + public static Object toHibernateValue( Component entity, Property property ) throws PageException { + PageContext pc = CFMLEngineFactory.getInstance().getThreadPageContext(); + Object value = entity.getComponentScope().get( CommonUtil.createKey( property.getName() ), property.getDefault() ); + + // Short-circuit if the value is null, no need to do any further processing. + if ( value == null ) { + return value; + } + + Struct meta = ( Struct ) property.getMetaData(); + String ormType = CommonUtil.toString( meta.get( CommonUtil.ORMTYPE, "" ) ); + String fieldType = !ormType.trim().isEmpty() ? ormType : property.getType(); + + // If the value is an empty string, and the field type is not one of the types that should be converted to null + if ( value instanceof String && + ( ( String ) value ).trim().isEmpty() && + !CFConstants.UnsavedValue.EMPTY_TO_NULL.contains( fieldType ) ) { + return null; + } + + return toHibernateValue( pc, value, fieldType ); + } + + /** + * translate CFMl specific types to Hibernate/SQL specific types + * + * @param type + * @param value + * @param isArray + * + * @throws PageException + */ + public static Object toSQL( Type type, Object value, RefBoolean isArray ) throws PageException { + int t = toSQLType( type.getName(), Types.OTHER ); + return toSQL( t, value, isArray ); + } + + /** + * translate CFMl specific type to SQL specific types + * + * @param sqlType + * @param value + * @param isArray + * + * @throws PageException + */ + private static Object toSQL( int sqlType, Object value, RefBoolean isArray ) throws PageException { + if ( sqlType == Types.OTHER && value instanceof PersistentCollection ) { + return value; + } + + if ( isArray != null ) + isArray.setValue( false ); + + Boolean _isArray = null; + boolean hasType = sqlType != Types.OTHER; + + // first we try to convert without any checking + if ( hasType ) { + try { + return CommonUtil.toSqlType( CommonUtil.toSQLItem( value, sqlType ) ); + } catch ( PageException pe ) { + _isArray = CommonUtil.isArray( value ); + if ( !_isArray.booleanValue() ) + throw pe; + } + } + + // already a hibernate type + + // can only be null if type is other + if ( _isArray == null && !CommonUtil.isArray( value ) ) { + return value; + } + + // at this point it is for sure that the value is an array + if ( isArray != null ) + isArray.setValue( true ); + Array src = CommonUtil.toArray( value ); + Iterator it = src.valueIterator(); + ArrayList trg = new ArrayList<>(); + Object v; + while ( it.hasNext() ) { + v = it.next(); + if ( v == null ) + continue; + if ( hasType ) { + trg.add( CommonUtil.toSqlType( CommonUtil.toSQLItem( v, sqlType ) ) ); + } else + trg.add( v ); + } + return trg; + } + + public static lucee.runtime.type.Query toQuery( PageContext pc, HibernateORMSession session, Object obj, String name ) + throws PageException { + Query qry = null; + // a single entity + if ( !CommonUtil.isArray( obj ) ) { + qry = toQuery( pc, session, HibernateCaster.toComponent( obj ), name, null, 1, 1 ); + } + + // a array of entities + else { + Array arr = CommonUtil.toArray( obj ); + int len = arr.size(); + if ( len > 0 ) { + Iterator it = arr.valueIterator(); + int row = 1; + while ( it.hasNext() ) { + qry = toQuery( pc, session, HibernateCaster.toComponent( it.next() ), name, qry, len, row++ ); + } + } else + qry = CommonUtil.createQuery( new Collection.Key[ 0 ], 0, "orm" ); + } + + if ( qry == null ) { + if ( !Util.isEmpty( name ) ) { + String message = String.format( "there is no entity inheritance that match the name [%s]", name ); + throw ExceptionUtil.createException( session, null, message, null ); + } else { + throw ExceptionUtil.createException( session, null, "cannot create query", null ); + } + } + return qry; + } + + private static Query toQuery( PageContext pc, HibernateORMSession session, Component cfc, String entityName, Query qry, + int rowcount, int row ) throws PageException { + // inheritance mapping + if ( !Util.isEmpty( entityName ) ) { + return inheritance( pc, session, cfc, qry, entityName ); + } + return populateQuery( pc, session, cfc, qry ); + } + + private static Query populateQuery( PageContext pc, HibernateORMSession session, Component cfc, Query qry ) + throws PageException { + Property[] properties = CommonUtil.getProperties( cfc, true, true, false, false ); + String dsn = CommonUtil.getDataSourceName( pc, cfc ); + ComponentScope scope = cfc.getComponentScope(); + HibernateORMEngine engine = ( HibernateORMEngine ) session.getEngine(); + + // init + if ( qry == null ) { + SessionFactory factory = session.getRawSessionFactory( dsn ); + ClassMetadata md = factory.getClassMetadata( getEntityName( cfc ) ); + Array names = CommonUtil.createArray(); + Array types = CommonUtil.createArray(); + String name; + int t; + Object obj; + Struct sct; + String fieldType; + for ( int i = 0; i < properties.length; i++ ) { + obj = properties[ i ].getMetaData(); + if ( obj instanceof Struct ) { + sct = ( Struct ) obj; + fieldType = CommonUtil.toString( sct.get( CommonUtil.FIELDTYPE, null ), null ); + if ( CFConstants.Relationships.isRelationshipType( fieldType ) ) { + continue; + } + + } + + name = HibernateUtil.validateColumnName( md, properties[ i ].getName(), null ); + names.append( name ); + if ( name != null ) { + + t = HibernateCaster.toSQLType( HibernateUtil.getPropertyType( md, name ).getName(), NULL ); + if ( t == NULL ) + types.append( "object" ); + else + types.append( CFMLEngineFactory.getInstance().getDBUtil().toStringType( t ) ); + } else + types.append( "object" ); + } + + qry = CommonUtil.createQuery( names, types, 0, getEntityName( cfc ) ); + + } + // check + else if ( engine.getMode() == ORMEngine.MODE_STRICT && !qry.getName().equals( getEntityName( cfc ) ) ) { + throw ExceptionUtil.createException( session, null, "can only merge entities of the same kind to a query", null ); + } + + // populate + Key[] names = CFMLEngineFactory.getInstance().getDBUtil().getColumnNames( qry ); + + int row = qry.addRow(); + for ( int i = 0; i < names.length; i++ ) { + qry.setAtEL( names[ i ], row, scope.get( names[ i ], null ) ); + } + return qry; + } + + private static Query inheritance( PageContext pc, HibernateORMSession session, Component cfc, Query qry, String entityName ) + throws PageException { + Property[] properties = cfc.getProperties( true, false, false, false ); + ComponentScope scope = cfc.getComponentScope(); + Object value; + Array arr; + for ( int i = 0; i < properties.length; i++ ) { + value = scope.get( CommonUtil.createKey( properties[ i ].getName() ), null ); + if ( value instanceof Component ) { + qry = inheritance( pc, session, qry, cfc, ( Component ) value, entityName ); + } else if ( CommonUtil.isArray( value ) ) { + arr = CommonUtil.toArray( value ); + Iterator it = arr.valueIterator(); + while ( it.hasNext() ) { + value = it.next(); + if ( value instanceof Component ) { + qry = inheritance( pc, session, qry, cfc, ( Component ) value, entityName ); + } + } + } + } + return qry; + } + + private static Query inheritance( PageContext pc, HibernateORMSession session, Query qry, Component parent, Component child, + String entityName ) throws PageException { + if ( getEntityName( child ).equalsIgnoreCase( entityName ) ) + return populateQuery( pc, session, child, qry ); + return inheritance( pc, session, child, qry, entityName );// @TODO: geh ACF auch so tief? + } + + public static Component toComponent( Object obj ) throws PageException { + return CommonUtil.toComponent( obj ); + } } diff --git a/extension/src/main/java/ortus/extension/orm/event/EventListenerIntegrator.java b/extension/src/main/java/ortus/extension/orm/event/EventListenerIntegrator.java index 55de612a..9bf0449e 100644 --- a/extension/src/main/java/ortus/extension/orm/event/EventListenerIntegrator.java +++ b/extension/src/main/java/ortus/extension/orm/event/EventListenerIntegrator.java @@ -27,30 +27,26 @@ import org.hibernate.event.spi.FlushEventListener; import org.hibernate.event.spi.PostDeleteEvent; import org.hibernate.event.spi.PostDeleteEventListener; -import org.hibernate.event.spi.PreInsertEvent; -import org.hibernate.event.spi.PreInsertEventListener; import org.hibernate.event.spi.PostInsertEvent; import org.hibernate.event.spi.PostInsertEventListener; import org.hibernate.event.spi.PostLoadEvent; import org.hibernate.event.spi.PostLoadEventListener; -import org.hibernate.event.spi.PreUpdateEvent; -import org.hibernate.event.spi.PreUpdateEventListener; import org.hibernate.event.spi.PostUpdateEvent; import org.hibernate.event.spi.PostUpdateEventListener; import org.hibernate.event.spi.PreDeleteEvent; import org.hibernate.event.spi.PreDeleteEventListener; +import org.hibernate.event.spi.PreInsertEvent; +import org.hibernate.event.spi.PreInsertEventListener; import org.hibernate.event.spi.PreLoadEvent; import org.hibernate.event.spi.PreLoadEventListener; +import org.hibernate.event.spi.PreUpdateEvent; +import org.hibernate.event.spi.PreUpdateEventListener; import org.hibernate.integrator.spi.Integrator; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.service.spi.SessionFactoryServiceRegistry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ortus.extension.orm.HibernateCaster; -import ortus.extension.orm.mapping.CFConstants; -import ortus.extension.orm.util.CommonUtil; - import lucee.loader.engine.CFMLEngine; import lucee.loader.engine.CFMLEngineFactory; import lucee.runtime.Component; @@ -61,6 +57,9 @@ import lucee.runtime.type.Collection.Key; import lucee.runtime.type.Struct; import lucee.runtime.type.UDF; +import ortus.extension.orm.HibernateCaster; +import ortus.extension.orm.mapping.CFConstants; +import ortus.extension.orm.util.CommonUtil; /** * Integrate Hibernate events with the EventHandler and component listener UDFs. @@ -69,375 +68,390 @@ * insertion into the database. This same method will also be invoked on the EventHandler component, if configured. */ public class EventListenerIntegrator - implements Integrator, PreInsertEventListener, PostInsertEventListener, PreDeleteEventListener, PostDeleteEventListener, - DeleteEventListener, PreUpdateEventListener, PostUpdateEventListener, PreLoadEventListener, PostLoadEventListener, - FlushEventListener, AutoFlushEventListener, ClearEventListener, DirtyCheckEventListener, EvictEventListener { - - private static final long serialVersionUID = -5954121166467541422L; - - private static final Logger logger = LoggerFactory.getLogger(EventListenerIntegrator.class); - - /** - * The EventHandler CFC defined in the application's `this.ormSettings.eventHandler`. - */ - private Component globalEventListener; - - public static final Key ON_EVICT = CommonUtil.createKey( "onEvict" ); - public static final Key ON_DIRTY_CHECK = CommonUtil.createKey( "onDirtyCheck" ); - public static final Key ON_DELETE = CommonUtil.createKey( "onDelete" ); - public static final Key ON_CLEAR = CommonUtil.createKey( "onClear" ); - public static final Key ON_AUTO_FLUSH = CommonUtil.createKey( "onAutoFlush" ); - public static final Key ON_FLUSH = CommonUtil.createKey( "onFlush" ); - public static final Key PRE_INSERT = CommonUtil.createKey( "preInsert" ); - public static final Key PRE_UPDATE = CommonUtil.createKey( "preUpdate" ); - public static final Key POST_LOAD = CommonUtil.createKey( "postLoad" ); - public static final Key PRE_LOAD = CommonUtil.createKey( "preLoad" ); - public static final Key POST_DELETE = CommonUtil.createKey( "postDelete" ); - public static final Key PRE_DELETE = CommonUtil.createKey( "preDelete" ); - public static final Key POST_UPDATE = CommonUtil.createKey( "postUpdate" ); - public static final Key POST_INSERT = CommonUtil.createKey( "postInsert" ); - - @Override - public void integrate( Metadata metadata, SessionFactoryImplementor sessionFactory, - SessionFactoryServiceRegistry serviceRegistry ) { - EventListenerRegistry eventListenerRegistry = serviceRegistry.getService( EventListenerRegistry.class ); - - eventListenerRegistry.prependListeners( EventType.PRE_INSERT, this ); - eventListenerRegistry.prependListeners( EventType.POST_INSERT, this ); - - eventListenerRegistry.prependListeners( EventType.PRE_DELETE, this ); - eventListenerRegistry.prependListeners( EventType.POST_DELETE, this ); - eventListenerRegistry.prependListeners( EventType.DELETE, this ); - - eventListenerRegistry.prependListeners( EventType.PRE_UPDATE, this ); - eventListenerRegistry.prependListeners( EventType.POST_UPDATE, this ); - - eventListenerRegistry.prependListeners( EventType.PRE_LOAD, this ); - eventListenerRegistry.prependListeners( EventType.POST_LOAD, this ); - - eventListenerRegistry.prependListeners( EventType.AUTO_FLUSH, this ); - eventListenerRegistry.prependListeners( EventType.FLUSH, this ); - - eventListenerRegistry.prependListeners( EventType.EVICT, this ); - eventListenerRegistry.prependListeners( EventType.CLEAR, this ); - - eventListenerRegistry.prependListeners( EventType.DIRTY_CHECK, this ); - } - - @Override - public void disintegrate( SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry ) { - - } - - /** - * Set the "Global" EventHandler to fire on all events in the application. - * - * @param globalEventListener - * Instantiated Lucee Component object. - */ - public void setGlobalEventListener( Component globalEventListener ) { - this.globalEventListener = globalEventListener; - } - - @Override - public boolean requiresPostCommitHanding( EntityPersister arg0 ) { - // TODO Auto-generated method stub - return false; - } - - @Override - public boolean onPreInsert( PreInsertEvent event ) { - String[] propertyNames = event.getPersister().getEntityMetamodel().getPropertyNames(); - Struct state = entityStateToStruct( propertyNames, event.getState() ); - fireEventOnGlobalListener( EventListenerIntegrator.PRE_INSERT, event.getEntity(), event, state ); - - fireOnEntity( event.getEntity(), EventListenerIntegrator.PRE_INSERT, event, null ); - - Component entity = CommonUtil.toComponent( event.getEntity(), null ); - Object[] stateValues = event.getState(); - if ( entity != null ) - persistEntityChangesToState( stateValues, propertyNames, entity ); - - new Nullability(event.getSession()) - .checkNullability(stateValues, event.getPersister(), NullabilityCheckType.CREATE); - - return false; - } - - @Override - public void onPostInsert( PostInsertEvent event ) { - fireEventOnGlobalListener( EventListenerIntegrator.POST_INSERT, event.getEntity(), event, null ); - - fireOnEntity( event.getEntity(), EventListenerIntegrator.POST_INSERT, event, null ); - } - - // PreDeleteEventListener - @Override - public boolean onPreDelete( PreDeleteEvent event ) { - fireEventOnGlobalListener( EventListenerIntegrator.PRE_DELETE, event.getEntity(), event, null ); - - fireOnEntity( event.getEntity(), EventListenerIntegrator.PRE_DELETE, event, null ); - return false; - } - - // PostDeleteEventListener - @Override - public void onPostDelete( PostDeleteEvent event ) { - fireEventOnGlobalListener( EventListenerIntegrator.POST_DELETE, event.getEntity(), event, null ); - - fireOnEntity( event.getEntity(), EventListenerIntegrator.POST_DELETE, event, null ); - } - - // PreUpdateEventListener - @Override - public boolean onPreUpdate( PreUpdateEvent event ) { - String[] propertyNames = event.getPersister().getEntityMetamodel().getPropertyNames(); - Struct oldState = entityStateToStruct( propertyNames, event.getOldState() ); - fireEventOnGlobalListener( EventListenerIntegrator.PRE_UPDATE, event.getEntity(), event, oldState ); - - fireOnEntity( event.getEntity(), EventListenerIntegrator.PRE_UPDATE, event, oldState ); - Component entity = CommonUtil.toComponent( event.getEntity(), null ); - Object[] stateValues = event.getState(); - if ( entity != null ) - persistEntityChangesToState( stateValues, propertyNames, entity ); - new Nullability(event.getSession()) - .checkNullability(stateValues, event.getPersister(), NullabilityCheckType.CREATE); - return false; - } - - // PostUpdateEventListener - @Override - public void onPostUpdate( PostUpdateEvent event ) { - fireEventOnGlobalListener( EventListenerIntegrator.POST_UPDATE, event.getEntity(), event, null ); - - fireOnEntity( event.getEntity(), EventListenerIntegrator.POST_UPDATE, event, null ); - } - - // PreLoadEventListener - @Override - public void onPreLoad( PreLoadEvent event ) { - fireEventOnGlobalListener( EventListenerIntegrator.PRE_LOAD, event.getEntity(), event, null ); - - fireOnEntity( event.getEntity(), EventListenerIntegrator.PRE_LOAD, event, null ); - } - - // PostLoadEventListener - @Override - public void onPostLoad( PostLoadEvent event ) { - fireEventOnGlobalListener( EventListenerIntegrator.POST_LOAD, event.getEntity(), event, null ); - - fireOnEntity( event.getEntity(), EventListenerIntegrator.POST_LOAD, event, null ); - } - - @Override - public void onFlush( FlushEvent event ) throws HibernateException { - // Sadly, the FlushEvent does not allow / provide a method to retrieve the entity. - Object entity = null; - fireEventOnGlobalListener( EventListenerIntegrator.ON_FLUSH, entity, event, null ); - } - - @Override - public void onAutoFlush( AutoFlushEvent event ) throws HibernateException { - // Sadly, the AutoFlushEvent does not allow / provide a method to retrieve the entity. - Object entity = null; - fireEventOnGlobalListener( EventListenerIntegrator.ON_AUTO_FLUSH, entity, event, null ); - } - - @Override - public void onClear( ClearEvent event ) { - // Sadly, the ClearEvent does not allow / provide a method to retrieve the entity. - Object entity = null; - fireEventOnGlobalListener( EventListenerIntegrator.ON_CLEAR, entity, event, null ); - } - - @Override - public void onDelete( DeleteEvent event ) throws HibernateException { - Object entity = event.getObject(); - fireEventOnGlobalListener( EventListenerIntegrator.ON_DELETE, entity, event, null ); - } - - @Override - @SuppressWarnings("rawtypes") - public void onDelete( DeleteEvent event, Set transientEntities ) throws HibernateException { - Object entity = event.getObject(); - // @TODO: handle transientEntities - fireEventOnGlobalListener( EventListenerIntegrator.ON_DELETE, entity, event, null ); - } - - @Override - public void onDirtyCheck( DirtyCheckEvent event ) throws HibernateException { - // Sadly, the DirtyCheckEvent does not allow / provide a method to retrieve the entity. - Object entity = null; - fireEventOnGlobalListener( EventListenerIntegrator.ON_DIRTY_CHECK, entity, event, null ); - } - - @Override - public void onEvict( EvictEvent event ) throws HibernateException { - // Sadly, the EvictEvent does not allow / provide a method to retrieve the entity. - Object entity = null; - fireEventOnGlobalListener( EventListenerIntegrator.ON_EVICT, entity, event, null ); - } - - /** - * Retrieve the configured "Global" event listener. - * - * i.e., the Component configured in `this.ormSettings.eventHandler`. - * - * @return The configured Component to use as this application's event handler. - */ - public Component getGlobalEventListener() { - return globalEventListener; - } - - /** - * Fire the event listener UDF on the configured global EventHandler listener. - *

- * If no global event handler is configured, will exit. - * - * @param name - * event type name to fire, for example "preInsert" or "preDelete" - * @param entity - * the entity from event.getEntity() - * @param event - * the Hibernate event object. - * @param data - * A struct of data to pass to the event - */ - public void fireEventOnGlobalListener( Key name, Object entity, AbstractEvent event, Struct data ) { - if ( globalEventListener == null ) { - return; - } - - if ( logger.isInfoEnabled() ){ - logger.atInfo().log( String.format("Firing event %s listener method on global listener %s", name, globalEventListener.getName() ) ); - } - fireOnComponent( getGlobalEventListener(), name, entity, data, event ); - } - - /** - * Fire the event listener UDF, if found, on the entity component - * - * @param listener - * the Lucee Component on which to fire this listener method - * @param name - * event type name to fire, for example "preInsert" or "preDelete" - * @param event - * the Hibernate event object. - * @param data - * A struct of data to pass to the event - */ - public void fireOnEntity( Object entity, Key name, AbstractEvent event, Struct data ) { - Component listener = CommonUtil.toComponent( entity, null ); - if ( listener != null ) { - if ( logger.isInfoEnabled() ){ - logger.atInfo().log( String.format("Firing event %s listener method on entity %s", name, listener.getName() ) ); - } - fireOnComponent( listener, name, data, event ); - } - } - - /** - * See if the given component has a method matching the given name. - * - * @param comp - * Lucee Component to look on, for example events.EventHandler. - * @param methodName - * Method name to look for, for example "preInsert" - * - * @return true if method found - */ - private boolean componentHasMethod( Component comp, Collection.Key methodName ) { - return comp.get( methodName, null ) instanceof UDF; - } - - private void fireOnComponent( Component cfc, Key name, Object... args ) { - if ( !componentHasMethod( cfc, name ) ) { - return; - } - CFMLEngine engine = CFMLEngineFactory.getInstance(); - try { - PageContext pc = engine.getThreadPageContext(); - cfc.call( pc, name, args ); - } catch ( PageException pe ) { - throw engine.getCastUtil().toPageRuntimeException( pe ); - } - } - - /** - * Merge the provided arrays of properties and values into a CFML-friendly struct. - * - * @param properties - * Array of property names, usually retrieved from event.getPersister().getPropertyNames() - * @param values - * Array of property values, either retrieved from event.getPersister().getPropertyValues(), - * event.getState() or event.getOldState() - * - * @return A struct - */ - private Struct entityStateToStruct( String[] properties, Object[] values ) { - Struct entityState = CommonUtil.createStruct(); - - if ( values != null && properties != null && values.length == properties.length ) { - for ( int i = 0; i < values.length; i++ ) { - entityState.setEL( CommonUtil.createKey( properties[ i ] ), values[ i ] ); - } - } - return entityState; - } - - /** - * Loop over the provided state properties and persist any entity changes to the state object. - *

- * Useful in the case of a "Pre database operation event", where the state to be committed has already been - * recorded, and any changes made during a `onPreInsert()` or `onPreUpdate()` event will need to be made in the - * `state` object in order to affect a change in what will be persisted. - *

- * Currently used in onPreInsert and onPreUpdate. - * - * See http://anshuiitk.blogspot.com/2010/11/hibernate-pre-database-opertaion-event.html - * - * @param state - * The entity state to persist, from event.getState(). {@link org.hibernate.event.spi.PreInsertEvent} - * @param stateProperties - * Array of properties to update, matching the order of the `state[]` fields. - * @param entity - * The entity to pull a potentially altered value from. - */ - private void persistEntityChangesToState( Object[] state, String[] stateProperties, Component entity ) { - if ( logger.isDebugEnabled() ){ - logger.atDebug().log( String.format( "persisting entity state changes on state properties %s", Arrays.toString(stateProperties) ) ); - } - try { - Property[] properties = entity.getProperties( true, true, false, false ); - if ( logger.isDebugEnabled() ){ - String propNames = Arrays.stream(properties).map( el -> el.getName()).collect(Collectors.joining(",")); - logger.atDebug().log( String.format( "persisting entity state changes for entity properties %s", propNames ) ); - } - for ( int n = 0; n < stateProperties.length; ++n ) { - final String currentProperty = stateProperties[ n ]; - Optional property = Arrays.stream( properties ) - .filter( prop -> prop.getName().equals( currentProperty ) ) - .filter( prop -> { - return !isRelationshipField( prop ); - }) - .findFirst(); - if ( property.isPresent() ) { - Property theprop = property.get(); - Object value = entity.getComponentScope().get( CommonUtil.createKey( theprop.getName() ), null ); - if ( value != null ) { - state[ n ] = HibernateCaster.toHibernateValue( entity, theprop ); - } - } - } - } catch ( Exception e ) { - throw new RuntimeException( - String.format( "Error populating event state for persistance in [%s] entity pre-event listener method: %s", entity.getName(), e.getMessage() ), e ); - } - } - - private boolean isRelationshipField( Property prop ){ - Struct meta = (Struct) prop.getMetaData(); - String fieldType = CommonUtil.toString( meta.get( CommonUtil.FIELDTYPE, null ), null ); - return fieldType != null && CFConstants.Relationships.isRelationshipType(fieldType); - } + implements Integrator, PreInsertEventListener, PostInsertEventListener, PreDeleteEventListener, PostDeleteEventListener, + DeleteEventListener, PreUpdateEventListener, PostUpdateEventListener, PreLoadEventListener, PostLoadEventListener, + FlushEventListener, AutoFlushEventListener, ClearEventListener, DirtyCheckEventListener, EvictEventListener { + + private static final long serialVersionUID = -5954121166467541422L; + + private static final Logger logger = LoggerFactory.getLogger( EventListenerIntegrator.class ); + + /** + * The EventHandler CFC defined in the application's `this.ormSettings.eventHandler`. + */ + private Component globalEventListener; + + public static final Key ON_EVICT = CommonUtil.createKey( "onEvict" ); + public static final Key ON_DIRTY_CHECK = CommonUtil.createKey( "onDirtyCheck" ); + public static final Key ON_DELETE = CommonUtil.createKey( "onDelete" ); + public static final Key ON_CLEAR = CommonUtil.createKey( "onClear" ); + public static final Key ON_AUTO_FLUSH = CommonUtil.createKey( "onAutoFlush" ); + public static final Key ON_FLUSH = CommonUtil.createKey( "onFlush" ); + public static final Key PRE_INSERT = CommonUtil.createKey( "preInsert" ); + public static final Key PRE_UPDATE = CommonUtil.createKey( "preUpdate" ); + public static final Key POST_LOAD = CommonUtil.createKey( "postLoad" ); + public static final Key PRE_LOAD = CommonUtil.createKey( "preLoad" ); + public static final Key POST_DELETE = CommonUtil.createKey( "postDelete" ); + public static final Key PRE_DELETE = CommonUtil.createKey( "preDelete" ); + public static final Key POST_UPDATE = CommonUtil.createKey( "postUpdate" ); + public static final Key POST_INSERT = CommonUtil.createKey( "postInsert" ); + + @Override + public void integrate( Metadata metadata, SessionFactoryImplementor sessionFactory, + SessionFactoryServiceRegistry serviceRegistry ) { + EventListenerRegistry eventListenerRegistry = serviceRegistry.getService( EventListenerRegistry.class ); + + eventListenerRegistry.prependListeners( EventType.PRE_INSERT, this ); + eventListenerRegistry.prependListeners( EventType.POST_INSERT, this ); + + eventListenerRegistry.prependListeners( EventType.PRE_DELETE, this ); + eventListenerRegistry.prependListeners( EventType.POST_DELETE, this ); + eventListenerRegistry.prependListeners( EventType.DELETE, this ); + + eventListenerRegistry.prependListeners( EventType.PRE_UPDATE, this ); + eventListenerRegistry.prependListeners( EventType.POST_UPDATE, this ); + + eventListenerRegistry.prependListeners( EventType.PRE_LOAD, this ); + eventListenerRegistry.prependListeners( EventType.POST_LOAD, this ); + + eventListenerRegistry.prependListeners( EventType.AUTO_FLUSH, this ); + eventListenerRegistry.prependListeners( EventType.FLUSH, this ); + + eventListenerRegistry.prependListeners( EventType.EVICT, this ); + eventListenerRegistry.prependListeners( EventType.CLEAR, this ); + + eventListenerRegistry.prependListeners( EventType.DIRTY_CHECK, this ); + } + + @Override + public void disintegrate( SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry ) { + // Not used, call by Hibernate to shutdown listeners. We don't need to do anything here. + } + + /** + * Set the "Global" EventHandler to fire on all events in the application. + * Called by {@link ortus.extension.orm.HibernateORMEngine#configureEventHandler()} + * + * @param globalEventListener Instantiated Lucee Component object. + */ + public void setGlobalEventListener( Component globalEventListener ) { + this.globalEventListener = globalEventListener; + } + + /** + * @see https://docs.jboss.org/hibernate/orm/5.4/javadocs/org/hibernate/event/spi/PostCommitUpdateEventListener.html + */ + @Override + public boolean requiresPostCommitHanding( EntityPersister persister ) { + // Indicate whether post-commit handling is required + return false; + } + + /** + * Fired on a pre-insert event. + * + * @see https://docs.jboss.org/hibernate/orm/5.4/javadocs/org/hibernate/event/spi/PreInsertEvent.html + */ + @Override + public boolean onPreInsert( PreInsertEvent event ) { + // The properties of the entity in declared order, important, the same as the state[] array. + String[] propertyNames = event.getPersister().getEntityMetamodel().getPropertyNames(); + Struct state = entityStateToStruct( propertyNames, event.getState() ); + Component entityCFC = CommonUtil.toComponent( event.getEntity(), null ); + + fireEventOnGlobalListener( EventListenerIntegrator.PRE_INSERT, event.getEntity(), event, state ); + fireOnEntity( entityCFC, EventListenerIntegrator.PRE_INSERT, event, null ); + + // Convert state changes from CFML -> Java + Object[] stateValues = event.getState(); + persistEntityChangesToState( stateValues, propertyNames, entityCFC ); + + // This is done to provide nullability checks for new entities and it's relationships + // Implements the algorithm for validating property values for illegal null values + new Nullability( event.getSession() ) + .checkNullability( stateValues, event.getPersister(), NullabilityCheckType.CREATE ); + + return false; + } + + @Override + public void onPostInsert( PostInsertEvent event ) { + fireEventOnGlobalListener( EventListenerIntegrator.POST_INSERT, event.getEntity(), event, null ); + fireOnEntity( event.getEntity(), EventListenerIntegrator.POST_INSERT, event, null ); + } + + // PreDeleteEventListener + @Override + public boolean onPreDelete( PreDeleteEvent event ) { + fireEventOnGlobalListener( EventListenerIntegrator.PRE_DELETE, event.getEntity(), event, null ); + fireOnEntity( event.getEntity(), EventListenerIntegrator.PRE_DELETE, event, null ); + return false; + } + + // PostDeleteEventListener + @Override + public void onPostDelete( PostDeleteEvent event ) { + fireEventOnGlobalListener( EventListenerIntegrator.POST_DELETE, event.getEntity(), event, null ); + fireOnEntity( event.getEntity(), EventListenerIntegrator.POST_DELETE, event, null ); + } + + // PreUpdateEventListener + @Override + public boolean onPreUpdate( PreUpdateEvent event ) { + // The properties of the entity in declared order, important, the same as the state[] array. + String[] propertyNames = event.getPersister().getEntityMetamodel().getPropertyNames(); + Struct oldState = entityStateToStruct( propertyNames, event.getOldState() ); + Component entityCFC = CommonUtil.toComponent( event.getEntity(), null ); + + fireEventOnGlobalListener( EventListenerIntegrator.PRE_UPDATE, event.getEntity(), event, oldState ); + fireOnEntity( entityCFC, EventListenerIntegrator.PRE_UPDATE, event, oldState ); + + // Convert state changes from CFML -> Java + Object[] stateValues = event.getState(); + persistEntityChangesToState( stateValues, propertyNames, entityCFC ); + + // This is done to provide nullability checks for new entities and it's relationships + // Implements the algorithm for validating property values for illegal null values + new Nullability( event.getSession() ) + .checkNullability( stateValues, event.getPersister(), NullabilityCheckType.CREATE ); + + return false; + } + + // PostUpdateEventListener + @Override + public void onPostUpdate( PostUpdateEvent event ) { + fireEventOnGlobalListener( EventListenerIntegrator.POST_UPDATE, event.getEntity(), event, null ); + fireOnEntity( event.getEntity(), EventListenerIntegrator.POST_UPDATE, event, null ); + } + + // PreLoadEventListener + @Override + public void onPreLoad( PreLoadEvent event ) { + fireEventOnGlobalListener( EventListenerIntegrator.PRE_LOAD, event.getEntity(), event, null ); + fireOnEntity( event.getEntity(), EventListenerIntegrator.PRE_LOAD, event, null ); + } + + // PostLoadEventListener + @Override + public void onPostLoad( PostLoadEvent event ) { + fireEventOnGlobalListener( EventListenerIntegrator.POST_LOAD, event.getEntity(), event, null ); + fireOnEntity( event.getEntity(), EventListenerIntegrator.POST_LOAD, event, null ); + } + + @Override + public void onFlush( FlushEvent event ) throws HibernateException { + // Sadly, the FlushEvent does not allow / provide a method to retrieve the entity. + Object entity = null; + fireEventOnGlobalListener( EventListenerIntegrator.ON_FLUSH, entity, event, null ); + } + + @Override + public void onAutoFlush( AutoFlushEvent event ) throws HibernateException { + // Sadly, the AutoFlushEvent does not allow / provide a method to retrieve the entity. + Object entity = null; + fireEventOnGlobalListener( EventListenerIntegrator.ON_AUTO_FLUSH, entity, event, null ); + } + + @Override + public void onClear( ClearEvent event ) { + // Sadly, the ClearEvent does not allow / provide a method to retrieve the entity. + Object entity = null; + fireEventOnGlobalListener( EventListenerIntegrator.ON_CLEAR, entity, event, null ); + } + + @Override + public void onDelete( DeleteEvent event ) throws HibernateException { + Object entity = event.getObject(); + fireEventOnGlobalListener( EventListenerIntegrator.ON_DELETE, entity, event, null ); + } + + @Override + @SuppressWarnings( "rawtypes" ) + public void onDelete( DeleteEvent event, Set transientEntities ) throws HibernateException { + Object entity = event.getObject(); + // @TODO: handle transientEntities + fireEventOnGlobalListener( EventListenerIntegrator.ON_DELETE, entity, event, null ); + } + + @Override + public void onDirtyCheck( DirtyCheckEvent event ) throws HibernateException { + // Sadly, the DirtyCheckEvent does not allow / provide a method to retrieve the entity. + Object entity = null; + fireEventOnGlobalListener( EventListenerIntegrator.ON_DIRTY_CHECK, entity, event, null ); + } + + @Override + public void onEvict( EvictEvent event ) throws HibernateException { + // Sadly, the EvictEvent does not allow / provide a method to retrieve the entity. + Object entity = null; + fireEventOnGlobalListener( EventListenerIntegrator.ON_EVICT, entity, event, null ); + } + + /** + * Retrieve the configured "Global" event listener. + * + * i.e., the Component configured in `this.ormSettings.eventHandler`. + * + * @return The configured Component to use as this application's event handler. + */ + public Component getGlobalEventListener() { + return globalEventListener; + } + + /** + * Fire the event listener UDF on the configured global EventHandler listener. + *

+ * If no global event handler is configured, will exit. + * + * @param name + * event type name to fire, for example "preInsert" or "preDelete" + * @param entity + * the entity from event.getEntity() + * @param event + * the Hibernate event object. + * @param data + * A struct of data to pass to the event + */ + public void fireEventOnGlobalListener( Key name, Object entity, AbstractEvent event, Struct data ) { + if ( globalEventListener == null ) { + return; + } + + if ( logger.isInfoEnabled() ) { + logger.atInfo().log( String.format( "Firing event %s listener method on global listener %s", name, globalEventListener.getName() ) ); + } + + fireOnComponent( getGlobalEventListener(), name, entity, data, event ); + } + + /** + * Fire the event listener UDF, if found, on the entity component + * + * @param listener the Lucee Component on which to fire this listener method + * @param name event type name to fire, for example "preInsert" or "preDelete" + * @param event the Hibernate event object. + * @param data A struct of data to pass to the event + */ + public void fireOnEntity( Object entity, Key name, AbstractEvent event, Struct data ) { + + // if entity is already a Component, just use it, else convert it to a component + Component listener = ( entity instanceof Component ) ? ( Component ) entity : CommonUtil.toComponent( entity, null ); + + if ( listener != null ) { + if ( logger.isInfoEnabled() ) { + logger.atInfo().log( String.format( "Firing event %s listener method on entity %s", name, listener.getName() ) ); + } + fireOnComponent( listener, name, data, event ); + } + } + + /** + * See if the given component has a method matching the given name. + * + * @param comp + * Lucee Component to look on, for example events.EventHandler. + * @param methodName + * Method name to look for, for example "preInsert" + * + * @return true if method found + */ + private boolean componentHasMethod( Component comp, Collection.Key methodName ) { + return comp.get( methodName, null ) instanceof UDF; + } + + private void fireOnComponent( Component cfc, Key name, Object... args ) { + if ( !componentHasMethod( cfc, name ) ) { + return; + } + CFMLEngine engine = CFMLEngineFactory.getInstance(); + try { + PageContext pc = engine.getThreadPageContext(); + cfc.call( pc, name, args ); + } catch ( PageException pe ) { + throw engine.getCastUtil().toPageRuntimeException( pe ); + } + } + + /** + * Merge the provided arrays of properties and values into a CFML-friendly struct. + * + * @param properties Array of property names, usually retrieved from event.getPersister().getPropertyNames() + * @param values Array of property values, either retrieved from event.getPersister().getPropertyValues(), + * event.getState() or event.getOldState() + * + * @return A struct + */ + private Struct entityStateToStruct( String[] properties, Object[] values ) { + Struct entityState = CommonUtil.createStruct(); + + if ( values != null && properties != null && values.length == properties.length ) { + for ( int i = 0; i < values.length; i++ ) { + entityState.setEL( CommonUtil.createKey( properties[ i ] ), values[ i ] ); + } + } + + return entityState; + } + + /** + * Loop over the provided state properties and persist any entity changes to the state object. + *

+ * Useful in the case of a "Pre database operation event", where the state to be committed has already been + * recorded, and any changes made during a `onPreInsert()` or `onPreUpdate()` event will need to be made in the + * `state` object in order to affect a change in what will be persisted. + *

+ * Currently used in onPreInsert and onPreUpdate. + * + * See http://anshuiitk.blogspot.com/2010/11/hibernate-pre-database-opertaion-event.html + * + * @param state The entity state to persist, from event.getState(). {@link org.hibernate.event.spi.PreInsertEvent} + * @param stateProperties Array of properties to update, matching the order of the `state[]` fields. + * @param entity The entity to pull a potentially altered value from. + */ + private void persistEntityChangesToState( Object[] state, String[] stateProperties, Component entity ) { + if ( logger.isDebugEnabled() ) { + logger.atDebug().log( String.format( "persisting entity state changes on state properties %s", Arrays.toString( stateProperties ) ) ); + } + + try { + Property[] cfcProperties = entity.getProperties( true, true, false, false ); + + if ( logger.isDebugEnabled() ) { + String propNames = Arrays.stream( cfcProperties ).map( Property::getName ).collect( Collectors.joining( "," ) ); + logger.atDebug().log( String.format( "persisting entity state changes for entity properties %s", propNames ) ); + } + + for ( int n = 0; n < stateProperties.length; ++n ) { + // Find the appropriate CFC entity property from the Hibernate data + final String currentProperty = stateProperties[ n ]; + Optional property = Arrays.stream( cfcProperties ) + .filter( prop -> prop.getName().equalsIgnoreCase( currentProperty ) ) + .filter( prop -> !isRelationshipField( prop ) ) + .findFirst(); + // Cast it to the right value + if ( property.isPresent() ) { + state[ n ] = HibernateCaster.toHibernateValue( entity, property.get() ); + } + } + + } catch ( Exception e ) { + throw new RuntimeException( + String.format( "Error populating event state for persistance in [%s] entity pre-event listener method: %s", entity.getName(), e.getMessage() ), + e ); + } + } + + /** + * Determine if the given property is a relationship field. + * + * @param prop The entity CFC property to check + * + * @return true if the property is a relationship field, false otherwise. + */ + private boolean isRelationshipField( Property prop ) { + Struct meta = ( Struct ) prop.getMetaData(); + String fieldType = CommonUtil.toString( meta.get( CommonUtil.FIELDTYPE, null ), null ); + return fieldType != null && CFConstants.Relationships.isRelationshipType( fieldType ); + } } diff --git a/extension/src/main/java/ortus/extension/orm/mapping/CFConstants.java b/extension/src/main/java/ortus/extension/orm/mapping/CFConstants.java index d26f79e7..16cf4696 100644 --- a/extension/src/main/java/ortus/extension/orm/mapping/CFConstants.java +++ b/extension/src/main/java/ortus/extension/orm/mapping/CFConstants.java @@ -1,217 +1,247 @@ package ortus.extension.orm.mapping; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; public final class CFConstants { - private CFConstants() { - throw new IllegalStateException( "Utility class; please don't instantiate!" ); - } - - public static class CacheUse { - public static final String READ_ONLY = "read-only"; - public static final String NONSTRICT_READ_WRITE = "nonstrict-read-write"; - public static final String READ_WRITE = "read-write"; - public static final String TRANSACTIONAL = "transactional"; - - public static final String ATTRIBUTE_NAME = "cacheuse"; - public static final String HBM_KEY = "usage"; - - public static List getPossibleValues(){ - return Stream.of(READ_ONLY, NONSTRICT_READ_WRITE, READ_WRITE, TRANSACTIONAL).collect(Collectors.toList()); - } - public static boolean isValidValue( String value ){ - return getPossibleValues().contains( value ); - } - } - - public static class CollectionType { - public static final String ARRAY = "array"; - public static final String BAG = "bag"; - public static final String STRUCT = "struct"; - public static final String MAP = "map"; - - public static final String ATTRIBUTE_NAME = "collectiontype"; - public static final String HBM_KEY = "type"; - - public static boolean isBagType( String typeName ){ - return ARRAY.equalsIgnoreCase(typeName) || BAG.equalsIgnoreCase(typeName); - } - public static boolean isMapType( String typeName ){ - return STRUCT.equalsIgnoreCase(typeName) || MAP.equalsIgnoreCase(typeName); - } - public static List getPossibleValues(){ - return Stream.of(ARRAY, BAG, STRUCT, MAP).collect(Collectors.toList()); - } - public static boolean isValidValue( String value ){ - return getPossibleValues().contains( value ); - } - } - - public static class VersionDataType { - public static final String INTEGER = "integer"; - public static final String INT = "int"; - public static final String LONG = "long"; - public static final String SHORT = "short"; - - public static final String ATTRIBUTE_NAME = "datatype"; - public static final String HBM_KEY = "type"; - - public static List getPossibleValues(){ - return Stream.of(INTEGER, INT, LONG, SHORT).collect(Collectors.toList()); - } - public static boolean isValidValue( String value ){ - return getPossibleValues().contains( value ); - } - } - - public static class VersionUnsavedValue extends CFConstants.UnsavedValue { - public static final String NEGATIVE = "negative"; - - public static List getPossibleValues(){ - return Stream.of(NULL, UNDEFINED,NEGATIVE).collect(Collectors.toList()); - } - } - - public static class Fetch { - public static final String JOIN = "join"; - public static final String SELECT = "select"; - - public static final String ATTRIBUTE_NAME = "fetch"; - public static final String HBM_KEY = "fetch"; - - public static List getPossibleValues(){ - return Stream.of(JOIN, SELECT).collect(Collectors.toList()); - } - public static boolean isValidValue( String value ){ - return getPossibleValues().contains( value ); - } - } - - public static class Generated { - public static final String ALWAYS = "always"; - public static final String INSERT = "insert"; - public static final String NEVER = "never"; - - public static final String ATTRIBUTE_NAME = "generated"; - public static final String HBM_KEY = "generated"; - - public static List getPossibleValues(){ - return Stream.of(ALWAYS, INSERT, NEVER).collect(Collectors.toList()); - } - public static boolean isValidValue( String value ){ - return getPossibleValues().contains( value ); - } - } - - public static class Lazy { - public static final String PROXY = "proxy"; - public static final String NO_PROXY = "no-proxy"; - public static final String TRUE = "true"; - public static final String FALSE = "false"; - public static final String EXTRA = "extra"; - - public static final String ATTRIBUTE_NAME = "lazy"; - public static final String HBM_KEY = "lazy"; - - public static List getPossibleForRelationship(){ - return Stream.of(PROXY, NO_PROXY, TRUE, FALSE).collect(Collectors.toList()); - } - - public static List getPossibleForSimple(){ - return Stream.of(TRUE, FALSE, EXTRA).collect(Collectors.toList()); - } - - public static boolean isValidForRelationship( String value ){ - return getPossibleForRelationship().contains( value ); - } - - public static boolean isValidForSimple( String value ){ - return getPossibleForSimple().contains( value ); - } - } - - public static class OptimisticLock { - public static final String ALL = "all"; - public static final String DIRTY = "dirty"; - public static final String NONE = "none"; - public static final String VERSION = "version"; - - public static final String ATTRIBUTE_NAME = "optimisticLock"; - public static final String HBM_KEY = "optimistic-lock"; - - public static List getPossibleValues(){ - return Stream.of(ALL, DIRTY, NONE, VERSION).collect(Collectors.toList()); - } - public static boolean isValidValue( String value ){ - return getPossibleValues().contains( value ); - } - } - - public static class Source { - public static final String VM = "vm"; - public static final String DB = "db"; - - public static final String ATTRIBUTE_NAME = "source"; - public static final String HBM_KEY = "source"; - - public static List getPossibleValues(){ - return Stream.of(VM, DB).collect(Collectors.toList()); - } - public static boolean isValidValue( String value ){ - return getPossibleValues().contains( value ); - } - } - - public static class UnsavedValue { - public static final String NULL = "null"; - public static final String UNDEFINED = "undefined"; - - public static final String ATTRIBUTE_NAME = "source"; - public static final String HBM_KEY = "source"; - - public static List getPossibleValues(){ - return Stream.of(NULL, UNDEFINED).collect(Collectors.toList()); - } - public static boolean isValidValue( String value ){ - return getPossibleValues().contains( value ); - } - } - - /** - * @TODO: Move this into some CFConstants class, or somewhere that both HBMCreator and HibernateCaster can reference it. - * @TODO: @nextMajorRelease, Migrate to Map.of() or Map.ofEntries in Java 9+ - */ - public static class Relationships { - public static final String ONE_TO_MANY = "one-to-many"; - public static final String MANY_TO_MANY = "many-to-many"; - public static final String MANY_TO_ONE = "many-to-one"; - public static final String ONE_TO_ONE = "one-to-one"; - public static final String KEY_MANY_TO_ONE = "key-many-to-one"; - - private Relationships() { - throw new IllegalStateException( "Utility class; please don't instantiate!" ); - } - - /** - * Identify whether the given string denotes one of the four main relationship types: - * - one-to-many - * - one-to-one - * - many-to-many - * - many-to-one - * - * @param fieldType Field type string from a persistent property, like "one-to-many" or "id" - * @return Boolean - */ - public static boolean isRelationshipType( String fieldType ){ - return - ONE_TO_MANY.equalsIgnoreCase(fieldType) || - MANY_TO_MANY.equalsIgnoreCase(fieldType) || - MANY_TO_ONE.equalsIgnoreCase(fieldType) || - ONE_TO_ONE.equalsIgnoreCase(fieldType) || - KEY_MANY_TO_ONE.equalsIgnoreCase(fieldType); - } - } + private CFConstants() { + throw new IllegalStateException( "Utility class; please don't instantiate!" ); + } + + public static class CacheUse { + + public static final String READ_ONLY = "read-only"; + public static final String NONSTRICT_READ_WRITE = "nonstrict-read-write"; + public static final String READ_WRITE = "read-write"; + public static final String TRANSACTIONAL = "transactional"; + + public static final String ATTRIBUTE_NAME = "cacheuse"; + public static final String HBM_KEY = "usage"; + + public static List getPossibleValues() { + return Stream.of( READ_ONLY, NONSTRICT_READ_WRITE, READ_WRITE, TRANSACTIONAL ).collect( Collectors.toList() ); + } + + public static boolean isValidValue( String value ) { + return getPossibleValues().contains( value ); + } + } + + public static class CollectionType { + + public static final String ARRAY = "array"; + public static final String BAG = "bag"; + public static final String STRUCT = "struct"; + public static final String MAP = "map"; + + public static final String ATTRIBUTE_NAME = "collectiontype"; + public static final String HBM_KEY = "type"; + + public static boolean isBagType( String typeName ) { + return ARRAY.equalsIgnoreCase( typeName ) || BAG.equalsIgnoreCase( typeName ); + } + + public static boolean isMapType( String typeName ) { + return STRUCT.equalsIgnoreCase( typeName ) || MAP.equalsIgnoreCase( typeName ); + } + + public static List getPossibleValues() { + return Stream.of( ARRAY, BAG, STRUCT, MAP ).collect( Collectors.toList() ); + } + + public static boolean isValidValue( String value ) { + return getPossibleValues().contains( value ); + } + } + + public static class VersionDataType { + + public static final String INTEGER = "integer"; + public static final String INT = "int"; + public static final String LONG = "long"; + public static final String SHORT = "short"; + + public static final String ATTRIBUTE_NAME = "datatype"; + public static final String HBM_KEY = "type"; + + public static List getPossibleValues() { + return Stream.of( INTEGER, INT, LONG, SHORT ).collect( Collectors.toList() ); + } + + public static boolean isValidValue( String value ) { + return getPossibleValues().contains( value ); + } + } + + public static class VersionUnsavedValue extends CFConstants.UnsavedValue { + + public static final String NEGATIVE = "negative"; + + public static List getPossibleValues() { + return Stream.of( NULL, UNDEFINED, NEGATIVE ).collect( Collectors.toList() ); + } + } + + public static class Fetch { + + public static final String JOIN = "join"; + public static final String SELECT = "select"; + + public static final String ATTRIBUTE_NAME = "fetch"; + public static final String HBM_KEY = "fetch"; + + public static List getPossibleValues() { + return Stream.of( JOIN, SELECT ).collect( Collectors.toList() ); + } + + public static boolean isValidValue( String value ) { + return getPossibleValues().contains( value ); + } + } + + public static class Generated { + + public static final String ALWAYS = "always"; + public static final String INSERT = "insert"; + public static final String NEVER = "never"; + + public static final String ATTRIBUTE_NAME = "generated"; + public static final String HBM_KEY = "generated"; + + public static List getPossibleValues() { + return Stream.of( ALWAYS, INSERT, NEVER ).collect( Collectors.toList() ); + } + + public static boolean isValidValue( String value ) { + return getPossibleValues().contains( value ); + } + } + + public static class Lazy { + + public static final String PROXY = "proxy"; + public static final String NO_PROXY = "no-proxy"; + public static final String TRUE = "true"; + public static final String FALSE = "false"; + public static final String EXTRA = "extra"; + + public static final String ATTRIBUTE_NAME = "lazy"; + public static final String HBM_KEY = "lazy"; + + public static List getPossibleForRelationship() { + return Stream.of( PROXY, NO_PROXY, TRUE, FALSE ).collect( Collectors.toList() ); + } + + public static List getPossibleForSimple() { + return Stream.of( TRUE, FALSE, EXTRA ).collect( Collectors.toList() ); + } + + public static boolean isValidForRelationship( String value ) { + return getPossibleForRelationship().contains( value ); + } + + public static boolean isValidForSimple( String value ) { + return getPossibleForSimple().contains( value ); + } + } + + public static class OptimisticLock { + + public static final String ALL = "all"; + public static final String DIRTY = "dirty"; + public static final String NONE = "none"; + public static final String VERSION = "version"; + + public static final String ATTRIBUTE_NAME = "optimisticLock"; + public static final String HBM_KEY = "optimistic-lock"; + + public static List getPossibleValues() { + return Stream.of( ALL, DIRTY, NONE, VERSION ).collect( Collectors.toList() ); + } + + public static boolean isValidValue( String value ) { + return getPossibleValues().contains( value ); + } + } + + public static class Source { + + public static final String VM = "vm"; + public static final String DB = "db"; + public static final boolean NOTNULL_DEFAULT = false; + + public static final String ATTRIBUTE_NAME = "source"; + public static final String HBM_KEY = "source"; + + public static List getPossibleValues() { + return Stream.of( VM, DB ).collect( Collectors.toList() ); + } + + public static boolean isValidValue( String value ) { + return getPossibleValues().contains( value ); + } + } + + public static class UnsavedValue { + + public static final String NULL = "null"; + public static final String UNDEFINED = "undefined"; + + public static final String ATTRIBUTE_NAME = "source"; + public static final String HBM_KEY = "source"; + + public static final Set EMPTY_TO_NULL = Collections.unmodifiableSet( + new HashSet<>( Arrays.asList( "string", "character", "text" ) ) + ); + + public static List getPossibleValues() { + return Stream.of( NULL, UNDEFINED ).collect( Collectors.toList() ); + } + + public static boolean isValidValue( String value ) { + return getPossibleValues().contains( value ); + } + } + + /** + * @TODO: Move this into some CFConstants class, or somewhere that both HBMCreator and HibernateCaster can reference it. + * @TODO: @nextMajorRelease, Migrate to Map.of() or Map.ofEntries in Java 9+ + */ + public static class Relationships { + + public static final String ONE_TO_MANY = "one-to-many"; + public static final String MANY_TO_MANY = "many-to-many"; + public static final String MANY_TO_ONE = "many-to-one"; + public static final String ONE_TO_ONE = "one-to-one"; + public static final String KEY_MANY_TO_ONE = "key-many-to-one"; + + private Relationships() { + throw new IllegalStateException( "Utility class; please don't instantiate!" ); + } + + /** + * Identify whether the given string denotes one of the four main relationship types: + * - one-to-many + * - one-to-one + * - many-to-many + * - many-to-one + * + * @param fieldType Field type string from a persistent property, like "one-to-many" or "id" + * + * @return Boolean + */ + public static boolean isRelationshipType( String fieldType ) { + return ONE_TO_MANY.equalsIgnoreCase( fieldType ) || + MANY_TO_MANY.equalsIgnoreCase( fieldType ) || + MANY_TO_ONE.equalsIgnoreCase( fieldType ) || + ONE_TO_ONE.equalsIgnoreCase( fieldType ) || + KEY_MANY_TO_ONE.equalsIgnoreCase( fieldType ); + } + } } diff --git a/extension/src/main/java/ortus/extension/orm/util/CommonUtil.java b/extension/src/main/java/ortus/extension/orm/util/CommonUtil.java index 953c931f..7845c3c4 100644 --- a/extension/src/main/java/ortus/extension/orm/util/CommonUtil.java +++ b/extension/src/main/java/ortus/extension/orm/util/CommonUtil.java @@ -13,15 +13,15 @@ import java.io.Writer; import java.lang.reflect.Method; import java.math.BigDecimal; -import java.util.Date; import java.nio.charset.Charset; import java.sql.Clob; -import java.util.Map; import java.util.ArrayList; import java.util.Calendar; +import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.TimeZone; import org.w3c.dom.Document; @@ -41,889 +41,892 @@ import lucee.runtime.db.SQL; import lucee.runtime.db.SQLItem; import lucee.runtime.exp.PageException; +import lucee.runtime.ext.function.Function; +import lucee.runtime.op.Castable; import lucee.runtime.type.Array; import lucee.runtime.type.Collection; import lucee.runtime.type.Collection.Key; +import lucee.runtime.type.ObjectWrap; import lucee.runtime.type.Query; import lucee.runtime.type.Struct; import lucee.runtime.type.dt.DateTime; import lucee.runtime.type.scope.Argument; -import lucee.runtime.ext.function.Function; import lucee.runtime.util.Cast; import lucee.runtime.util.Creation; import lucee.runtime.util.DBUtil; import lucee.runtime.util.Decision; import lucee.runtime.util.ORMUtil; import lucee.runtime.util.Operation; -import lucee.runtime.op.Castable; -import lucee.runtime.type.ObjectWrap; public class CommonUtil { - public static final Key ENTITY_NAME = CommonUtil.createKey( "entityname" ); - public static final Key FIELDTYPE = CommonUtil.createKey( "fieldtype" ); - public static final Key INIT = CommonUtil.createKey( "init" ); - private static final short INSPECT_UNDEFINED = ( short ) 4; /* ConfigImpl.INSPECT_UNDEFINED */ - private static Charset charset; - - private static Charset utf8Charset; - private static Charset utf16beCharset; - private static Charset uitf16leCharset; - - private static Cast caster; - private static Decision decision; - private static Creation creator; - private static Operation op; - private static lucee.runtime.util.ListUtil list; - private static DBUtil db; - private static ORMUtil orm; - - private static Method mGetDatasourceConnection; - private static Method mReleaseDatasourceConnection; - - private CommonUtil() { - throw new IllegalStateException( "Utility class; please don't instantiate!" ); - } - - public static Charset getCharset() { - if ( charset == null ) { - String strCharset = System.getProperty( "file.encoding" ); - if ( strCharset == null || strCharset.equalsIgnoreCase( "MacRoman" ) ) - strCharset = "cp1252"; - - if ( strCharset.equalsIgnoreCase( "utf-8" ) ) - charset = getUTF8Charset(); - else - charset = toCharset( strCharset ); - } - return charset; - } - - public static Charset getUTF8Charset() { - if ( utf8Charset == null ) - utf8Charset = toCharset( "UTF-8" ); - return utf8Charset; - } - - private static Charset getUTF16LECharset() { - if ( uitf16leCharset == null ) - uitf16leCharset = toCharset( "UTF-16LE" ); - return uitf16leCharset; - } - - private static Charset getUTF16BECharset() { - if ( utf16beCharset == null ) - utf16beCharset = toCharset( "UTF-16BE" ); - return utf16beCharset; - } - - private static Charset toCharset( String charset ) { - try { - return CFMLEngineFactory.getInstance().getCastUtil().toCharset( charset ); - } catch ( PageException pe ) { - throw CFMLEngineFactory.getInstance().getExceptionUtil().createPageRuntimeException( pe ); - } - } - - @SuppressWarnings("rawtypes") - public static Object castTo( PageContext pc, Class trgClass, Object obj ) throws PageException { - return caster().castTo( pc, trgClass, obj ); - } - - public static Array toArray( Object obj ) throws PageException { - return caster().toArray( obj ); - } - - public static Array toArray( Object obj, Array defaultValue ) { - return caster().toArray( obj, defaultValue ); - } - - public static Boolean toBoolean( String str ) throws PageException { - return caster().toBoolean( str ); - } - - public static Boolean toBoolean( String str, Boolean defaultValue ) { - return caster().toBoolean( str, defaultValue ); - } - - public static Boolean toBoolean( Object obj ) throws PageException { - return caster().toBoolean( obj ); - } - - public static Boolean toBoolean( Object obj, Boolean defaultValue ) { - return caster().toBoolean( obj, defaultValue ); - } - - public static Boolean toBooleanValue( String str ) throws PageException { - return caster().toBooleanValue( str ); - } - - public static Boolean toBooleanValue( String str, Boolean defaultValue ) { - return caster().toBooleanValue( str, defaultValue ); - } - - public static boolean toBooleanValue( Object obj ) throws PageException { - return caster().toBooleanValue( obj ); - } - - public static boolean toBooleanValue( Object obj, boolean defaultValue ) { - return caster().toBooleanValue( obj, defaultValue ); - } - - public static Component toComponent( Object obj ) throws PageException { - return caster().toComponent( obj ); - } - - public static Component toComponent( Object obj, Component defaultValue ) { - return caster().toComponent( obj, defaultValue ); - } - - public static Object toList( String[] arr, String delimiter ) { - return list().toList( arr, delimiter ); - } - - public static String toString( Object obj, String defaultValue ) { - return caster().toString( obj, defaultValue ); - } - - public static String toString( Object obj ) throws PageException { - return caster().toString( obj ); - } - - public static String toString( boolean b ) { - return caster().toString( b ); - } - - public static String toString( double d ) { - return caster().toString( d ); - } - - public static String toString( int i ) { - return caster().toString( i ); - } - - public static String toString( long l ) { - return caster().toString( l ); - } - - /** - * reads String data from File - * - * @param file - * @param charset - * - * @return readed string - * - * @throws IOException - */ - public static String toString( Resource file, Charset charset ) throws IOException { - try ( Reader r = getReader( file, charset ); ) { - return toString( r ); - } - } - - public static String toString( Reader reader ) throws IOException { - StringWriter sw = new StringWriter( 512 ); - copy( toBufferedReader( reader ), sw ); - sw.close(); - return sw.toString(); - } - - public static BufferedReader toBufferedReader( Reader r ) { - if ( r instanceof BufferedReader ) - return ( BufferedReader ) r; - return new BufferedReader( r ); - } - - private static final void copy( Reader r, Writer w ) throws IOException { - copy( r, w, 0xffff ); - } - - private static final void copy( Reader r, Writer w, int blockSize ) throws IOException { - char[] buffer = new char[ blockSize ]; - int len; - - while ( ( len = r.read( buffer ) ) != -1 ) - w.write( buffer, 0, len ); - } - - public static Reader getReader( Resource res, Charset charset ) throws IOException { - InputStream is = null; - try { - is = res.getInputStream(); - boolean markSupported = is.markSupported(); - if ( markSupported ) - is.mark( 4 ); - int first = is.read(); - int second = is.read(); - // FE FF UTF-16, big-endian - if ( first == 0xFE && second == 0xFF ) { - return getReaderForInputStream( is, getUTF16BECharset() ); - } - // FF FE UTF-16, little-endian - if ( first == 0xFF && second == 0xFE ) { - return getReaderForInputStream( is, getUTF16LECharset() ); - } - - int third = is.read(); - // EF BB BF UTF-8 - if ( first == 0xEF && second == 0xBB && third == 0xBF ) { - return getReaderForInputStream( is, getUTF8Charset() ); - } - - if ( markSupported ) { - is.reset(); - return getReaderForInputStream( is, charset ); - } - } catch ( IOException ioe ) { - closeEL( is ); - throw ioe; - } - - // when mark not supported return new reader - closeEL( is ); - is = null; - try { - is = res.getInputStream(); - } catch ( IOException ioe ) { - closeEL( is ); - throw ioe; - } - return getReaderForInputStream( is, charset ); - } - - private static Reader getReaderForInputStream( InputStream is, Charset cs ) { - // @TODO: This is a very bad pattern - setting and using class state on a util class from a static method. - if ( cs == null ) { - cs = getCharset(); - } - return new BufferedReader( new InputStreamReader( is, cs ) ); - } - - public static String[] toStringArray( String list, String delimiter ) { - return list().toStringArray( list().toArray( list, delimiter ), "" ); - } - - public static Float toFloat( Object obj ) throws PageException { - return caster().toFloat( obj ); - } - - public static Float toFloat( Object obj, Float defaultValue ) { - return caster().toFloat( obj, defaultValue ); - } - - public static float toFloatValue( Object obj ) throws PageException { - return caster().toFloatValue( obj ); - } - - public static float toFloatValue( Object obj, float defaultValue ) { - return caster().toFloatValue( obj, defaultValue ); - } - - public static Double toDouble( Object obj ) throws PageException { - return caster().toDouble( obj ); - } - - public static Double toDouble( Object obj, Double defaultValue ) { - return caster().toDouble( obj, defaultValue ); - } - - public static double toDoubleValue( Object obj ) throws PageException { - return caster().toDoubleValue( obj ); - } - - public static double toDoubleValue( Object obj, double defaultValue ) { - return caster().toDoubleValue( obj, defaultValue ); - } - - public static BigDecimal toBigDecimal( Object obj ) throws PageException { - return caster().toBigDecimal( obj ); - } - - public static BigDecimal toBigDecimal( Object obj, BigDecimal defaultValue ) { - return caster().toBigDecimal( obj, defaultValue ); - } - - public static Short toShort( Object obj ) throws PageException { - return caster().toShort( obj ); - } - - public static Short toShort( Object obj, Short defaultValue ) { - return caster().toShort( obj, defaultValue ); - } - - public static double toShortValue( Object obj ) throws PageException { - return caster().toShortValue( obj ); - } - - public static double toShortValue( Object obj, short defaultValue ) { - return caster().toShortValue( obj, defaultValue ); - } - - public static Integer toInteger( Object obj ) throws PageException { - return caster().toInteger( obj ); - } - - public static Integer toInteger( Object obj, Integer defaultValue ) { - return caster().toInteger( obj, defaultValue ); - } - - public static Long toLong( Object obj ) throws PageException { - return caster().toLong( obj ); - } - - public static Long toLong( Object obj, Long defaultValue ) { - return caster().toLong( obj, defaultValue ); - } - - public static long toLongValue( Object obj ) throws PageException { - return caster().toLongValue( obj ); - } - - public static long toLongValue( Object obj, long defaultValue ) { - return caster().toLongValue( obj, defaultValue ); - } - - public static byte[] toBinary( Object obj ) throws PageException { - return caster().toBinary( obj ); - } - - public static byte[] toBinary( Object obj, byte[] defaultValue ) { - return caster().toBinary( obj, defaultValue ); - } - - public static int toIntValue( Object obj ) throws PageException { - return caster().toIntValue( obj ); - } - - public static int toIntValue( Object obj, int defaultValue ) { - return caster().toIntValue( obj, defaultValue ); - } - - public static Array toArray( Argument arg ) { - Array trg = createArray(); - int[] keys = arg.intKeys(); - for ( int i = 0; i < keys.length; i++ ) { - trg.setEL( keys[ i ], arg.get( keys[ i ], null ) ); - } - return trg; - } - - public static Serializable toSerializable( Object obj ) throws PageException { - return caster().toSerializable( obj ); - } - - public static Serializable toSerializable( Object obj, Serializable defaultValue ) { - return caster().toSerializable( obj, defaultValue ); - } - - public static Struct toStruct( Object obj ) throws PageException { - return caster().toStruct( obj ); - } - - public static Struct toStruct( Object obj, Struct defaultValue ) { - return caster().toStruct( obj, defaultValue ); - } - - public static SQLItem toSQLItem( Object value, int type ) { - return db().toSQLItem( value, type ); - } - - public static SQL toSQL( String sql, SQLItem[] items ) { - return db().toSQL( sql, items ); - } - - public static Object toSqlType( SQLItem item ) throws PageException { - return db().toSqlType( item ); - } - - public static Object[] toNativeArray( Object obj ) throws PageException { - return caster().toNativeArray( obj ); - } - - public static Key toKey( String str ) { - return caster().toKey( str ); - } - - public static String toTypeName( Object obj ) { - return caster().toTypeName( obj ); - } - - public static Node toXML( Object obj ) throws PageException { - return XMLUtil.toNode( obj ); - } - - /** - * Parse XML file contents into an XML object ready for manipulation - * - * @param res Resource (File) to read - * @param cs Charset to use when parsing document - * - * @return XML Document - * - * @throws PageException - */ - public static Document toDocument( Resource res, Charset cs ) throws PageException { - return XMLUtil.parse( XMLUtil.toInputSource( res, cs ), null, false ); - } - - public static boolean isArray( Object obj ) { - return decision().isArray( obj ); - } - - public static boolean isStruct( Object obj ) { - return decision().isStruct( obj ); - } - - /** - * See if a given value is coercable to a string. - *

- * Blatantly copied from Lucee core because it's not in the Lucee loader, so we don't have access to run it without - * reflection. - * - * @link https://github.com/lucee/Lucee/blob/6.0/core/src/main/java/lucee/runtime/op/Decision.java#L964 - * - * @param o - * Value to compare - * - * @return Boolean, true if value is a String or castable to a String. - */ - public static boolean isString( Object o ) { - if ( o instanceof String ) - return true; - else if ( o instanceof Boolean ) - return true; - else if ( o instanceof Number ) - return true; - else if ( o instanceof Date ) - return true; - else if ( o instanceof Castable ) { - return !( ( Castable ) o ).castToString( "this is a unique string" ).equals("this is a unique string"); - - } else if ( o instanceof Clob ) - return true; - else if ( o instanceof Node ) - return true; - else if ( o instanceof Map || o instanceof List || o instanceof Function ) - return false; - else if ( o == null ) - return true; - else if ( o instanceof ObjectWrap ) - return isString( ( ( ObjectWrap ) o ).getEmbededObject( "" ) ); - return true; - } - - public static boolean isSimpleValue( Object obj ) { - return decision().isSimpleValue( obj ); - } - - public static boolean isCastableToBoolean( Object obj ) { - return decision().isCastableToBoolean( obj ); - } - - public static boolean isCastableToArray( Object o ) { - return decision().isCastableToArray( o ); - } - - public static boolean isCastableToStruct( Object o ) { - return decision().isCastableToStruct( o ); - } - - public static boolean isBinary( Object obj ) { - return decision().isBinary( obj ); - } - - public static boolean isBoolean( Object obj ) { - return decision().isBoolean( obj ); - } - - public static boolean isAnyType( String type ) { - return decision().isAnyType( type ); - } - - public static Array createArray() { - return creator().createArray(); - } - - public static DateTime createDateTime( long time ) { - return creator().createDateTime( time ); - } - - public static Property createProperty( String name, String type ) { - return creator().createProperty( name, type ); - } - - public static Struct createStruct() { - return creator().createStruct(); - } - - public static Collection.Key createKey( String key ) { - return creator().createKey( key ); - } - - public static Query createQuery( Collection.Key[] columns, int rows, String name ) throws PageException { - return creator().createQuery( columns, rows, name ); - } - - public static Query createQuery( Collection.Key[] columns, String[] types, int rows, String name ) throws PageException { - return creator().createQuery( columns, types, rows, name ); - } - - public static Query createQuery( Array names, Array types, int rows, String name ) throws PageException { - Collection.Key[] knames = new Collection.Key[ names.size() ]; - String[] ktypes = new String[ types.size() ]; - for ( int i = names.size() - 1; i >= 0; i-- ) { - knames[ i ] = caster().toKey( names.getE( i + 1 ) ); - ktypes[ i ] = caster().toString( types.getE( i + 1 ) ); - } - return creator().createQuery( knames, ktypes, rows, name ); - } - - public static RefBoolean createRefBoolean() { - return new RefBooleanImpl(); - } - - public static Key[] keys( Collection coll ) { - if ( coll == null ) - return new Key[ 0 ]; - Iterator it = coll.keyIterator(); - List rtn = new ArrayList<>(); - if ( it != null ) - while ( it.hasNext() ) { - rtn.add( it.next() ); - } - return rtn.toArray( new Key[ rtn.size() ] ); - } - - private static Creation creator() { - if ( creator == null ) - creator = CFMLEngineFactory.getInstance().getCreationUtil(); - return creator; - } - - private static Decision decision() { - if ( decision == null ) - decision = CFMLEngineFactory.getInstance().getDecisionUtil(); - return decision; - } - - static Cast caster() { - if ( caster == null ) - caster = CFMLEngineFactory.getInstance().getCastUtil(); - return caster; - } - - private static Operation op() { - if ( op == null ) - op = CFMLEngineFactory.getInstance().getOperatonUtil(); - return op; - } - - private static lucee.runtime.util.ListUtil list() { - if ( list == null ) - list = CFMLEngineFactory.getInstance().getListUtil(); - return list; - } - - private static ORMUtil orm() { - if ( orm == null ) - orm = CFMLEngineFactory.getInstance().getORMUtil(); - return orm; - } - - private static DBUtil db() { - if ( db == null ) - db = CFMLEngineFactory.getInstance().getDBUtil(); - return db; - } - - /** - * Integer Type that can be modified - */ - private static final class RefBooleanImpl implements RefBoolean {// @TODO: add interface Castable - - private boolean value; - - public RefBooleanImpl() { - } - - /** - * @param value - */ - public RefBooleanImpl( boolean value ) { - this.value = value; - } - - /** - * @param value - */ - @Override - public void setValue( boolean value ) { - this.value = value; - } - - /** - * @return returns value as Boolean Object - */ - @Override - public Boolean toBoolean() { - return value ? Boolean.TRUE : Boolean.FALSE; - } - - /** - * @return returns value as boolean value - */ - @Override - public boolean toBooleanValue() { - return value; - } - - @Override - public String toString() { - return value ? "true" : "false"; - } - } - - /** - * Get the datasource defined for the provided name, or the default if name is null. - * - * @param pc - * Lucee's PageContext object. - * @param name - * Datasource name, or null to retrieve the default - * - * @return A Datasource object - * - * @throws PageException - */ - public static DataSource getDataSource( PageContext pc, String name ) throws PageException { - if ( Util.isEmpty( name, true ) ) - return orm().getDefaultDataSource( pc ); - return pc.getDataSource( name ); - } - - public static DatasourceConnection getDatasourceConnection( PageContext pc, DataSource ds, String user, String pass, - boolean transactionSensitive ) throws PageException { - if ( transactionSensitive ) { - return pc.getDataSourceManager().getConnection( pc, ds, user, pass ); - - } - - DBUtil dbutil = db(); - try { - if ( mGetDatasourceConnection == null || dbutil.getClass() != mGetDatasourceConnection.getDeclaringClass() ) { - mGetDatasourceConnection = dbutil.getClass().getMethod( "getDatasourceConnection", PageContext.class, - DataSource.class, String.class, String.class, boolean.class ); - } - return ( DatasourceConnection ) mGetDatasourceConnection.invoke( dbutil, pc, ds, user, pass, false ); - } catch ( Exception e ) { - throw ExceptionUtil.toPageException( e ); - } - } - - public static void releaseDatasourceConnection( PageContext pc, DatasourceConnection dc, boolean transactionSensitive ) - throws PageException { - if ( transactionSensitive ) { - pc.getDataSourceManager().releaseConnection( pc, dc ); - return; - } - - DBUtil dbutil = db(); - try { - if ( mReleaseDatasourceConnection == null || dbutil.getClass() != mReleaseDatasourceConnection.getDeclaringClass() ) { - mReleaseDatasourceConnection = dbutil.getClass().getMethod( "releaseDatasourceConnection", PageContext.class, - DatasourceConnection.class, boolean.class ); - } - mReleaseDatasourceConnection.invoke( dbutil, pc, dc, false ); - } catch ( Exception e ) { - throw ExceptionUtil.toPageException( e ); - } - } - - public static Mapping createMapping( Config config, String virtual, String physical ) { - return creator().createMapping( config, virtual, physical, null, INSPECT_UNDEFINED, true, false, false, false, true, true, - null, -1, -1 ); - } - - public static String last( String list, String delimiter ) { - return list().last( list, delimiter, true ); - } - - public static int listFindNoCaseIgnoreEmpty( String list, String value, char delimiter ) { - return list().findNoCaseIgnoreEmpty( list, value, delimiter ); - } - - public static String[] trimItems( String[] arr ) { - for ( int i = 0; i < arr.length; i++ ) { - arr[ i ] = arr[ i ].trim(); - } - return arr; - } - - public static void setFirst( Node parent, Node node ) { - XMLUtil.setFirst( parent, node ); - } - - public static Property[] getProperties( Component c, boolean onlyPeristent, boolean includeBaseProperties, - boolean preferBaseProperties, boolean inheritedMappedSuperClassOnly ) { - return c.getProperties( onlyPeristent, includeBaseProperties, preferBaseProperties, inheritedMappedSuperClassOnly ); - } - - public static void write( Resource res, String string, Charset cs, boolean append ) throws IOException { - if ( cs == null ) - cs = getCharset(); - - try ( Writer writer = getWriter( res, cs, append ); ) { - writer.write( string ); - } - } - - public static Writer getWriter( Resource res, Charset charset, boolean append ) throws IOException { - OutputStream os = null; - try { - os = res.getOutputStream( append ); - } catch ( IOException ioe ) { - closeEL( os ); - throw ioe; - } - return getWriter( os, charset ); - } - - public static Writer getWriter( OutputStream os, Charset cs ) { - // @TODO: This is a very bad pattern - setting and using class state on a util class from a static method. - if ( cs == null ) { - getCharset(); - } - return new BufferedWriter( new OutputStreamWriter( os, getCharset() ) ); - } - - public static BufferedReader toBufferedReader( Resource res, Charset charset ) throws IOException { - return toBufferedReader( getReader( res, ( Charset ) null ) ); - } - - public static boolean equalsComplexEL( Object left, Object right ) { - return op().equalsComplexEL( left, right, false, true ); - } - - public static PageContext pc() { - return CFMLEngineFactory.getInstance().getThreadPageContext(); - } - - public static Config config() { - return pc().getConfig(); - } - - public static void closeEL( OutputStream os ) { - if ( os != null ) { - try { - os.close(); - } catch ( Exception t ) { - // @TODO: @nextMajorRelease consider dropping this catch block - } - } - } - - public static void closeEL( InputStream is ) { - try { - if ( is != null ) - is.close(); - } catch ( Exception t ) { - // @TODO: @nextMajorRelease consider dropping this catch block - } - } - - public static boolean isRelated( Property property ) { - return orm().isRelated( property ); - } - - public static Object convertToSimpleMap( String paramsStr ) { - return orm().convertToSimpleMap( paramsStr ); - } - - public static String getDataSourceName( PageContext pc, Component cfc ) throws PageException { - return orm().getDataSourceName( pc, cfc ); - } - - public static DataSource getDataSource( PageContext pc, Component cfc ) throws PageException { - return orm().getDataSource( pc, cfc ); - } - - public static boolean equals( Component l, Component r ) { - return orm().equals( l, r ); - } - - public static DataSource getDefaultDataSource( PageContext pc ) throws PageException { - return orm().getDefaultDataSource( pc ); - } - - public static Object getPropertyValue( Component cfc, String name, Object defaultValue ) { - return orm().getPropertyValue( cfc, name, defaultValue ); - } - - public static String toBase64( Object o ) throws PageException { - return caster().toBase64( o ); - } - - public static Locale toLocale( String strLocale ) throws PageException { - return caster().toLocale( strLocale ); - } - - /** - * Convert the given value to a java.util.TimeZone. - * - * @param value Value (likely a string value) to be parsed as a timezone. - * @param defaultValue Default value (default TimeZone) to use if value is not convertable. - * @throws PageException - */ - public static TimeZone toTimeZone( Object value, TimeZone defaultValue ) throws PageException { - return caster().toTimeZone( value, defaultValue ); - } - - public static Character toCharacter( Object value ) throws PageException { - return caster().toCharacter( value ); - } - - public static DateTime toDate( Object value, TimeZone timeZone ) throws PageException { - return caster().toDate( value, timeZone ); - } - - public static Calendar toCalendar( DateTime date, TimeZone timeZone, Locale locale ) { - return caster().toCalendar( date.getTime(), timeZone, locale ); - } - - /** - * Tests if this string starts with the specified prefix. - *

- * Blatantly copied from the Lucee core, since we don't have access to this method without reflection. - * - * @link https://github.com/lucee/Lucee/blob/6.0/core/src/main/java/lucee/commons/lang/StringUtil.java#L870 - * - * @param str - * string to check first char - * @param prefix - * the prefix. - * - * @return is first of given type - */ - public static boolean startsWith( String str, char prefix ) { - return str != null && str.length() > 0 && str.charAt( 0 ) == prefix; - } - - /** - * Tests if this string ends with the specified suffix. - *

- * Blatantly copied from the Lucee core, since we don't have access to this method without reflection. - * - * @link https://github.com/lucee/Lucee/blob/6.0/core/src/main/java/lucee/commons/lang/StringUtil.java#L870 - * - * @param str - * string to check first char - * @param suffix - * the suffix. - * - * @return is last of given type - */ - public static boolean endsWith( String str, char suffix ) { - return str != null && str.length() > 0 && str.charAt( str.length() - 1 ) == suffix; - } + public static final Key ENTITY_NAME = CommonUtil.createKey( "entityname" ); + public static final Key FIELDTYPE = CommonUtil.createKey( "fieldtype" ); + public static final Key INIT = CommonUtil.createKey( "init" ); + public static final Key ORMTYPE = CommonUtil.createKey( "ormtype" ); + public static final Key NOTNULL = CommonUtil.createKey( "notnull" ); + private static final short INSPECT_UNDEFINED = ( short ) 4; /* ConfigImpl.INSPECT_UNDEFINED */ + private static Charset charset; + + private static Charset utf8Charset; + private static Charset utf16beCharset; + private static Charset uitf16leCharset; + + private static Cast caster; + private static Decision decision; + private static Creation creator; + private static Operation op; + private static lucee.runtime.util.ListUtil list; + private static DBUtil db; + private static ORMUtil orm; + + private static Method mGetDatasourceConnection; + private static Method mReleaseDatasourceConnection; + + private CommonUtil() { + throw new IllegalStateException( "Utility class; please don't instantiate!" ); + } + + public static Charset getCharset() { + if ( charset == null ) { + String strCharset = System.getProperty( "file.encoding" ); + if ( strCharset == null || strCharset.equalsIgnoreCase( "MacRoman" ) ) + strCharset = "cp1252"; + + if ( strCharset.equalsIgnoreCase( "utf-8" ) ) + charset = getUTF8Charset(); + else + charset = toCharset( strCharset ); + } + return charset; + } + + public static Charset getUTF8Charset() { + if ( utf8Charset == null ) + utf8Charset = toCharset( "UTF-8" ); + return utf8Charset; + } + + private static Charset getUTF16LECharset() { + if ( uitf16leCharset == null ) + uitf16leCharset = toCharset( "UTF-16LE" ); + return uitf16leCharset; + } + + private static Charset getUTF16BECharset() { + if ( utf16beCharset == null ) + utf16beCharset = toCharset( "UTF-16BE" ); + return utf16beCharset; + } + + private static Charset toCharset( String charset ) { + try { + return CFMLEngineFactory.getInstance().getCastUtil().toCharset( charset ); + } catch ( PageException pe ) { + throw CFMLEngineFactory.getInstance().getExceptionUtil().createPageRuntimeException( pe ); + } + } + + @SuppressWarnings( "rawtypes" ) + public static Object castTo( PageContext pc, Class trgClass, Object obj ) throws PageException { + return caster().castTo( pc, trgClass, obj ); + } + + public static Array toArray( Object obj ) throws PageException { + return caster().toArray( obj ); + } + + public static Array toArray( Object obj, Array defaultValue ) { + return caster().toArray( obj, defaultValue ); + } + + public static Boolean toBoolean( String str ) throws PageException { + return caster().toBoolean( str ); + } + + public static Boolean toBoolean( String str, Boolean defaultValue ) { + return caster().toBoolean( str, defaultValue ); + } + + public static Boolean toBoolean( Object obj ) throws PageException { + return caster().toBoolean( obj ); + } + + public static Boolean toBoolean( Object obj, Boolean defaultValue ) { + return caster().toBoolean( obj, defaultValue ); + } + + public static Boolean toBooleanValue( String str ) throws PageException { + return caster().toBooleanValue( str ); + } + + public static Boolean toBooleanValue( String str, Boolean defaultValue ) { + return caster().toBooleanValue( str, defaultValue ); + } + + public static boolean toBooleanValue( Object obj ) throws PageException { + return caster().toBooleanValue( obj ); + } + + public static boolean toBooleanValue( Object obj, boolean defaultValue ) { + return caster().toBooleanValue( obj, defaultValue ); + } + + public static Component toComponent( Object obj ) throws PageException { + return caster().toComponent( obj ); + } + + public static Component toComponent( Object obj, Component defaultValue ) { + return caster().toComponent( obj, defaultValue ); + } + + public static Object toList( String[] arr, String delimiter ) { + return list().toList( arr, delimiter ); + } + + public static String toString( Object obj, String defaultValue ) { + return caster().toString( obj, defaultValue ); + } + + public static String toString( Object obj ) throws PageException { + return caster().toString( obj ); + } + + public static String toString( boolean b ) { + return caster().toString( b ); + } + + public static String toString( double d ) { + return caster().toString( d ); + } + + public static String toString( int i ) { + return caster().toString( i ); + } + + public static String toString( long l ) { + return caster().toString( l ); + } + + /** + * reads String data from File + * + * @param file + * @param charset + * + * @return readed string + * + * @throws IOException + */ + public static String toString( Resource file, Charset charset ) throws IOException { + try ( Reader r = getReader( file, charset ); ) { + return toString( r ); + } + } + + public static String toString( Reader reader ) throws IOException { + StringWriter sw = new StringWriter( 512 ); + copy( toBufferedReader( reader ), sw ); + sw.close(); + return sw.toString(); + } + + public static BufferedReader toBufferedReader( Reader r ) { + if ( r instanceof BufferedReader ) + return ( BufferedReader ) r; + return new BufferedReader( r ); + } + + private static final void copy( Reader r, Writer w ) throws IOException { + copy( r, w, 0xffff ); + } + + private static final void copy( Reader r, Writer w, int blockSize ) throws IOException { + char[] buffer = new char[ blockSize ]; + int len; + + while ( ( len = r.read( buffer ) ) != -1 ) + w.write( buffer, 0, len ); + } + + public static Reader getReader( Resource res, Charset charset ) throws IOException { + InputStream is = null; + try { + is = res.getInputStream(); + boolean markSupported = is.markSupported(); + if ( markSupported ) + is.mark( 4 ); + int first = is.read(); + int second = is.read(); + // FE FF UTF-16, big-endian + if ( first == 0xFE && second == 0xFF ) { + return getReaderForInputStream( is, getUTF16BECharset() ); + } + // FF FE UTF-16, little-endian + if ( first == 0xFF && second == 0xFE ) { + return getReaderForInputStream( is, getUTF16LECharset() ); + } + + int third = is.read(); + // EF BB BF UTF-8 + if ( first == 0xEF && second == 0xBB && third == 0xBF ) { + return getReaderForInputStream( is, getUTF8Charset() ); + } + + if ( markSupported ) { + is.reset(); + return getReaderForInputStream( is, charset ); + } + } catch ( IOException ioe ) { + closeEL( is ); + throw ioe; + } + + // when mark not supported return new reader + closeEL( is ); + is = null; + try { + is = res.getInputStream(); + } catch ( IOException ioe ) { + closeEL( is ); + throw ioe; + } + return getReaderForInputStream( is, charset ); + } + + private static Reader getReaderForInputStream( InputStream is, Charset cs ) { + // @TODO: This is a very bad pattern - setting and using class state on a util class from a static method. + if ( cs == null ) { + cs = getCharset(); + } + return new BufferedReader( new InputStreamReader( is, cs ) ); + } + + public static String[] toStringArray( String list, String delimiter ) { + return list().toStringArray( list().toArray( list, delimiter ), "" ); + } + + public static Float toFloat( Object obj ) throws PageException { + return caster().toFloat( obj ); + } + + public static Float toFloat( Object obj, Float defaultValue ) { + return caster().toFloat( obj, defaultValue ); + } + + public static float toFloatValue( Object obj ) throws PageException { + return caster().toFloatValue( obj ); + } + + public static float toFloatValue( Object obj, float defaultValue ) { + return caster().toFloatValue( obj, defaultValue ); + } + + public static Double toDouble( Object obj ) throws PageException { + return caster().toDouble( obj ); + } + + public static Double toDouble( Object obj, Double defaultValue ) { + return caster().toDouble( obj, defaultValue ); + } + + public static double toDoubleValue( Object obj ) throws PageException { + return caster().toDoubleValue( obj ); + } + + public static double toDoubleValue( Object obj, double defaultValue ) { + return caster().toDoubleValue( obj, defaultValue ); + } + + public static BigDecimal toBigDecimal( Object obj ) throws PageException { + return caster().toBigDecimal( obj ); + } + + public static BigDecimal toBigDecimal( Object obj, BigDecimal defaultValue ) { + return caster().toBigDecimal( obj, defaultValue ); + } + + public static Short toShort( Object obj ) throws PageException { + return caster().toShort( obj ); + } + + public static Short toShort( Object obj, Short defaultValue ) { + return caster().toShort( obj, defaultValue ); + } + + public static double toShortValue( Object obj ) throws PageException { + return caster().toShortValue( obj ); + } + + public static double toShortValue( Object obj, short defaultValue ) { + return caster().toShortValue( obj, defaultValue ); + } + + public static Integer toInteger( Object obj ) throws PageException { + return caster().toInteger( obj ); + } + + public static Integer toInteger( Object obj, Integer defaultValue ) { + return caster().toInteger( obj, defaultValue ); + } + + public static Long toLong( Object obj ) throws PageException { + return caster().toLong( obj ); + } + + public static Long toLong( Object obj, Long defaultValue ) { + return caster().toLong( obj, defaultValue ); + } + + public static long toLongValue( Object obj ) throws PageException { + return caster().toLongValue( obj ); + } + + public static long toLongValue( Object obj, long defaultValue ) { + return caster().toLongValue( obj, defaultValue ); + } + + public static byte[] toBinary( Object obj ) throws PageException { + return caster().toBinary( obj ); + } + + public static byte[] toBinary( Object obj, byte[] defaultValue ) { + return caster().toBinary( obj, defaultValue ); + } + + public static int toIntValue( Object obj ) throws PageException { + return caster().toIntValue( obj ); + } + + public static int toIntValue( Object obj, int defaultValue ) { + return caster().toIntValue( obj, defaultValue ); + } + + public static Array toArray( Argument arg ) { + Array trg = createArray(); + int[] keys = arg.intKeys(); + for ( int i = 0; i < keys.length; i++ ) { + trg.setEL( keys[ i ], arg.get( keys[ i ], null ) ); + } + return trg; + } + + public static Serializable toSerializable( Object obj ) throws PageException { + return caster().toSerializable( obj ); + } + + public static Serializable toSerializable( Object obj, Serializable defaultValue ) { + return caster().toSerializable( obj, defaultValue ); + } + + public static Struct toStruct( Object obj ) throws PageException { + return caster().toStruct( obj ); + } + + public static Struct toStruct( Object obj, Struct defaultValue ) { + return caster().toStruct( obj, defaultValue ); + } + + public static SQLItem toSQLItem( Object value, int type ) { + return db().toSQLItem( value, type ); + } + + public static SQL toSQL( String sql, SQLItem[] items ) { + return db().toSQL( sql, items ); + } + + public static Object toSqlType( SQLItem item ) throws PageException { + return db().toSqlType( item ); + } + + public static Object[] toNativeArray( Object obj ) throws PageException { + return caster().toNativeArray( obj ); + } + + public static Key toKey( String str ) { + return caster().toKey( str ); + } + + public static String toTypeName( Object obj ) { + return caster().toTypeName( obj ); + } + + public static Node toXML( Object obj ) throws PageException { + return XMLUtil.toNode( obj ); + } + + /** + * Parse XML file contents into an XML object ready for manipulation + * + * @param res Resource (File) to read + * @param cs Charset to use when parsing document + * + * @return XML Document + * + * @throws PageException + */ + public static Document toDocument( Resource res, Charset cs ) throws PageException { + return XMLUtil.parse( XMLUtil.toInputSource( res, cs ), null, false ); + } + + public static boolean isArray( Object obj ) { + return decision().isArray( obj ); + } + + public static boolean isStruct( Object obj ) { + return decision().isStruct( obj ); + } + + /** + * See if a given value is coercable to a string. + *

+ * Blatantly copied from Lucee core because it's not in the Lucee loader, so we don't have access to run it without + * reflection. + * + * @link https://github.com/lucee/Lucee/blob/6.0/core/src/main/java/lucee/runtime/op/Decision.java#L964 + * + * @param o + * Value to compare + * + * @return Boolean, true if value is a String or castable to a String. + */ + public static boolean isString( Object o ) { + if ( o instanceof String ) + return true; + else if ( o instanceof Boolean ) + return true; + else if ( o instanceof Number ) + return true; + else if ( o instanceof Date ) + return true; + else if ( o instanceof Castable ) { + return ! ( ( Castable ) o ).castToString( "this is a unique string" ).equals( "this is a unique string" ); + + } else if ( o instanceof Clob ) + return true; + else if ( o instanceof Node ) + return true; + else if ( o instanceof Map || o instanceof List || o instanceof Function ) + return false; + else if ( o == null ) + return true; + else if ( o instanceof ObjectWrap ) + return isString( ( ( ObjectWrap ) o ).getEmbededObject( "" ) ); + return true; + } + + public static boolean isSimpleValue( Object obj ) { + return decision().isSimpleValue( obj ); + } + + public static boolean isCastableToBoolean( Object obj ) { + return decision().isCastableToBoolean( obj ); + } + + public static boolean isCastableToArray( Object o ) { + return decision().isCastableToArray( o ); + } + + public static boolean isCastableToStruct( Object o ) { + return decision().isCastableToStruct( o ); + } + + public static boolean isBinary( Object obj ) { + return decision().isBinary( obj ); + } + + public static boolean isBoolean( Object obj ) { + return decision().isBoolean( obj ); + } + + public static boolean isAnyType( String type ) { + return decision().isAnyType( type ); + } + + public static Array createArray() { + return creator().createArray(); + } + + public static DateTime createDateTime( long time ) { + return creator().createDateTime( time ); + } + + public static Property createProperty( String name, String type ) { + return creator().createProperty( name, type ); + } + + public static Struct createStruct() { + return creator().createStruct(); + } + + public static Collection.Key createKey( String key ) { + return creator().createKey( key ); + } + + public static Query createQuery( Collection.Key[] columns, int rows, String name ) throws PageException { + return creator().createQuery( columns, rows, name ); + } + + public static Query createQuery( Collection.Key[] columns, String[] types, int rows, String name ) throws PageException { + return creator().createQuery( columns, types, rows, name ); + } + + public static Query createQuery( Array names, Array types, int rows, String name ) throws PageException { + Collection.Key[] knames = new Collection.Key[ names.size() ]; + String[] ktypes = new String[ types.size() ]; + for ( int i = names.size() - 1; i >= 0; i-- ) { + knames[ i ] = caster().toKey( names.getE( i + 1 ) ); + ktypes[ i ] = caster().toString( types.getE( i + 1 ) ); + } + return creator().createQuery( knames, ktypes, rows, name ); + } + + public static RefBoolean createRefBoolean() { + return new RefBooleanImpl(); + } + + public static Key[] keys( Collection coll ) { + if ( coll == null ) + return new Key[ 0 ]; + Iterator it = coll.keyIterator(); + List rtn = new ArrayList<>(); + if ( it != null ) + while ( it.hasNext() ) { + rtn.add( it.next() ); + } + return rtn.toArray( new Key[ rtn.size() ] ); + } + + private static Creation creator() { + if ( creator == null ) + creator = CFMLEngineFactory.getInstance().getCreationUtil(); + return creator; + } + + private static Decision decision() { + if ( decision == null ) + decision = CFMLEngineFactory.getInstance().getDecisionUtil(); + return decision; + } + + static Cast caster() { + if ( caster == null ) + caster = CFMLEngineFactory.getInstance().getCastUtil(); + return caster; + } + + private static Operation op() { + if ( op == null ) + op = CFMLEngineFactory.getInstance().getOperatonUtil(); + return op; + } + + private static lucee.runtime.util.ListUtil list() { + if ( list == null ) + list = CFMLEngineFactory.getInstance().getListUtil(); + return list; + } + + private static ORMUtil orm() { + if ( orm == null ) + orm = CFMLEngineFactory.getInstance().getORMUtil(); + return orm; + } + + private static DBUtil db() { + if ( db == null ) + db = CFMLEngineFactory.getInstance().getDBUtil(); + return db; + } + + /** + * Integer Type that can be modified + */ + private static final class RefBooleanImpl implements RefBoolean {// @TODO: add interface Castable + + private boolean value; + + public RefBooleanImpl() { + } + + /** + * @param value + */ + public RefBooleanImpl( boolean value ) { + this.value = value; + } + + /** + * @param value + */ + @Override + public void setValue( boolean value ) { + this.value = value; + } + + /** + * @return returns value as Boolean Object + */ + @Override + public Boolean toBoolean() { + return value ? Boolean.TRUE : Boolean.FALSE; + } + + /** + * @return returns value as boolean value + */ + @Override + public boolean toBooleanValue() { + return value; + } + + @Override + public String toString() { + return value ? "true" : "false"; + } + } + + /** + * Get the datasource defined for the provided name, or the default if name is null. + * + * @param pc + * Lucee's PageContext object. + * @param name + * Datasource name, or null to retrieve the default + * + * @return A Datasource object + * + * @throws PageException + */ + public static DataSource getDataSource( PageContext pc, String name ) throws PageException { + if ( Util.isEmpty( name, true ) ) + return orm().getDefaultDataSource( pc ); + return pc.getDataSource( name ); + } + + public static DatasourceConnection getDatasourceConnection( PageContext pc, DataSource ds, String user, String pass, + boolean transactionSensitive ) throws PageException { + if ( transactionSensitive ) { + return pc.getDataSourceManager().getConnection( pc, ds, user, pass ); + + } + + DBUtil dbutil = db(); + try { + if ( mGetDatasourceConnection == null || dbutil.getClass() != mGetDatasourceConnection.getDeclaringClass() ) { + mGetDatasourceConnection = dbutil.getClass().getMethod( "getDatasourceConnection", PageContext.class, + DataSource.class, String.class, String.class, boolean.class ); + } + return ( DatasourceConnection ) mGetDatasourceConnection.invoke( dbutil, pc, ds, user, pass, false ); + } catch ( Exception e ) { + throw ExceptionUtil.toPageException( e ); + } + } + + public static void releaseDatasourceConnection( PageContext pc, DatasourceConnection dc, boolean transactionSensitive ) + throws PageException { + if ( transactionSensitive ) { + pc.getDataSourceManager().releaseConnection( pc, dc ); + return; + } + + DBUtil dbutil = db(); + try { + if ( mReleaseDatasourceConnection == null || dbutil.getClass() != mReleaseDatasourceConnection.getDeclaringClass() ) { + mReleaseDatasourceConnection = dbutil.getClass().getMethod( "releaseDatasourceConnection", PageContext.class, + DatasourceConnection.class, boolean.class ); + } + mReleaseDatasourceConnection.invoke( dbutil, pc, dc, false ); + } catch ( Exception e ) { + throw ExceptionUtil.toPageException( e ); + } + } + + public static Mapping createMapping( Config config, String virtual, String physical ) { + return creator().createMapping( config, virtual, physical, null, INSPECT_UNDEFINED, true, false, false, false, true, true, + null, -1, -1 ); + } + + public static String last( String list, String delimiter ) { + return list().last( list, delimiter, true ); + } + + public static int listFindNoCaseIgnoreEmpty( String list, String value, char delimiter ) { + return list().findNoCaseIgnoreEmpty( list, value, delimiter ); + } + + public static String[] trimItems( String[] arr ) { + for ( int i = 0; i < arr.length; i++ ) { + arr[ i ] = arr[ i ].trim(); + } + return arr; + } + + public static void setFirst( Node parent, Node node ) { + XMLUtil.setFirst( parent, node ); + } + + public static Property[] getProperties( Component c, boolean onlyPeristent, boolean includeBaseProperties, + boolean preferBaseProperties, boolean inheritedMappedSuperClassOnly ) { + return c.getProperties( onlyPeristent, includeBaseProperties, preferBaseProperties, inheritedMappedSuperClassOnly ); + } + + public static void write( Resource res, String string, Charset cs, boolean append ) throws IOException { + if ( cs == null ) + cs = getCharset(); + + try ( Writer writer = getWriter( res, cs, append ); ) { + writer.write( string ); + } + } + + public static Writer getWriter( Resource res, Charset charset, boolean append ) throws IOException { + OutputStream os = null; + try { + os = res.getOutputStream( append ); + } catch ( IOException ioe ) { + closeEL( os ); + throw ioe; + } + return getWriter( os, charset ); + } + + public static Writer getWriter( OutputStream os, Charset cs ) { + // @TODO: This is a very bad pattern - setting and using class state on a util class from a static method. + if ( cs == null ) { + getCharset(); + } + return new BufferedWriter( new OutputStreamWriter( os, getCharset() ) ); + } + + public static BufferedReader toBufferedReader( Resource res, Charset charset ) throws IOException { + return toBufferedReader( getReader( res, ( Charset ) null ) ); + } + + public static boolean equalsComplexEL( Object left, Object right ) { + return op().equalsComplexEL( left, right, false, true ); + } + + public static PageContext pc() { + return CFMLEngineFactory.getInstance().getThreadPageContext(); + } + + public static Config config() { + return pc().getConfig(); + } + + public static void closeEL( OutputStream os ) { + if ( os != null ) { + try { + os.close(); + } catch ( Exception t ) { + // @TODO: @nextMajorRelease consider dropping this catch block + } + } + } + + public static void closeEL( InputStream is ) { + try { + if ( is != null ) + is.close(); + } catch ( Exception t ) { + // @TODO: @nextMajorRelease consider dropping this catch block + } + } + + public static boolean isRelated( Property property ) { + return orm().isRelated( property ); + } + + public static Object convertToSimpleMap( String paramsStr ) { + return orm().convertToSimpleMap( paramsStr ); + } + + public static String getDataSourceName( PageContext pc, Component cfc ) throws PageException { + return orm().getDataSourceName( pc, cfc ); + } + + public static DataSource getDataSource( PageContext pc, Component cfc ) throws PageException { + return orm().getDataSource( pc, cfc ); + } + + public static boolean equals( Component l, Component r ) { + return orm().equals( l, r ); + } + + public static DataSource getDefaultDataSource( PageContext pc ) throws PageException { + return orm().getDefaultDataSource( pc ); + } + + public static Object getPropertyValue( Component cfc, String name, Object defaultValue ) { + return orm().getPropertyValue( cfc, name, defaultValue ); + } + + public static String toBase64( Object o ) throws PageException { + return caster().toBase64( o ); + } + + public static Locale toLocale( String strLocale ) throws PageException { + return caster().toLocale( strLocale ); + } + + /** + * Convert the given value to a java.util.TimeZone. + * + * @param value Value (likely a string value) to be parsed as a timezone. + * @param defaultValue Default value (default TimeZone) to use if value is not convertable. + * + * @throws PageException + */ + public static TimeZone toTimeZone( Object value, TimeZone defaultValue ) throws PageException { + return caster().toTimeZone( value, defaultValue ); + } + + public static Character toCharacter( Object value ) throws PageException { + return caster().toCharacter( value ); + } + + public static DateTime toDate( Object value, TimeZone timeZone ) throws PageException { + return caster().toDate( value, timeZone ); + } + + public static Calendar toCalendar( DateTime date, TimeZone timeZone, Locale locale ) { + return caster().toCalendar( date.getTime(), timeZone, locale ); + } + + /** + * Tests if this string starts with the specified prefix. + *

+ * Blatantly copied from the Lucee core, since we don't have access to this method without reflection. + * + * @link https://github.com/lucee/Lucee/blob/6.0/core/src/main/java/lucee/commons/lang/StringUtil.java#L870 + * + * @param str + * string to check first char + * @param prefix + * the prefix. + * + * @return is first of given type + */ + public static boolean startsWith( String str, char prefix ) { + return str != null && str.length() > 0 && str.charAt( 0 ) == prefix; + } + + /** + * Tests if this string ends with the specified suffix. + *

+ * Blatantly copied from the Lucee core, since we don't have access to this method without reflection. + * + * @link https://github.com/lucee/Lucee/blob/6.0/core/src/main/java/lucee/commons/lang/StringUtil.java#L870 + * + * @param str + * string to check first char + * @param suffix + * the suffix. + * + * @return is last of given type + */ + public static boolean endsWith( String str, char suffix ) { + return str != null && str.length() > 0 && str.charAt( str.length() - 1 ) == suffix; + } }