001/**
002 *
003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
004 *
005 * This library is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU Lesser General Public
007 * License as published by the Free Software Foundation; either 
008 * version 2.1 of the License, or (at your option) any later version.
009 * 
010 * This library is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013 * Lesser General Public License for more details.
014 * 
015 * You should have received a copy of the GNU Lesser General Public 
016 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
017 * 
018 **/
019package lucee.runtime.orm.hibernate;
020
021import java.sql.DatabaseMetaData;
022import java.sql.ResultSet;
023import java.sql.SQLException;
024import java.util.ArrayList;
025import java.util.HashSet;
026import java.util.Iterator;
027import java.util.Set;
028
029import lucee.commons.io.res.Resource;
030import lucee.loader.util.Util;
031import lucee.runtime.Component;
032import lucee.runtime.PageContext;
033import lucee.runtime.PageContextImpl;
034import lucee.runtime.PageSource;
035import lucee.runtime.component.Property;
036import lucee.runtime.config.ConfigImpl;
037import lucee.runtime.db.DataSourceUtil;
038import lucee.runtime.db.DatasourceConnection;
039import lucee.runtime.exp.PageException;
040import lucee.runtime.orm.ORMConfiguration;
041import lucee.runtime.orm.ORMSession;
042import lucee.runtime.type.CastableStruct;
043import lucee.runtime.type.Collection;
044import lucee.runtime.type.Struct;
045import lucee.runtime.type.util.ComponentUtil;
046
047import org.hibernate.HibernateException;
048import org.hibernate.metadata.ClassMetadata;
049import org.hibernate.type.ComponentType;
050import org.hibernate.type.Type;
051
052
053public class HibernateUtil {
054
055        public static final short FIELDTYPE_ID=0;
056        public static final short FIELDTYPE_COLUMN=1;
057        public static final short FIELDTYPE_TIMESTAMP=2;
058        public static final short FIELDTYPE_RELATION=4;
059        public static final short FIELDTYPE_VERSION=8;
060        public static final short FIELDTYPE_COLLECTION=16;
061        
062        private static final String[] KEYWORDS=new String[]{"absolute","access","accessible","action","add","after","alias","all","allocate","allow","alter","analyze","and","any","application","are","array","as","asc","asensitive","assertion","associate","asutime","asymmetric","at","atomic","audit","authorization","aux","auxiliary","avg","backup","before","begin","between","bigint","binary","bit","bit_length","blob","boolean","both","breadth","break","browse","bufferpool","bulk","by","cache","call","called","capture","cardinality","cascade","cascaded","case","cast","catalog","ccsid","change","char","char_length","character","character_length","check","checkpoint","clob","close","cluster","clustered","coalesce","collate","collation","collection","collid","column","comment","commit","compress","compute","concat","condition","connect","connection","constraint","constraints","constructor","contains","containstable","continue","convert","corresponding","count","count_big","create","cross","cube","current","current_date","current_default_transform_group","current_lc_ctype","current_path","current_role","current_server","current_time","current_timestamp","current_timezone","current_transform_group_for_type","current_user","cursor","cycle","data","database","databases","date","day","day_hour","day_microsecond","day_minute","day_second","days","db2general","db2genrl","db2sql","dbcc","dbinfo","deallocate","dec","decimal","declare","default","defaults","deferrable","deferred","delayed","delete","deny","depth","deref","desc","describe","descriptor","deterministic","diagnostics","disallow","disconnect","disk","distinct","distinctrow","distributed","div","do","domain","double","drop","dsnhattr","dssize","dual","dummy","dump","dynamic","each","editproc","else","elseif","enclosed","encoding","end","end-exec","end-exec1","endexec","equals","erase","errlvl","escape","escaped","except","exception","excluding","exclusive","exec","execute","exists","exit","explain","external","extract","false","fenced","fetch","fieldproc","file","fillfactor","filter","final","first","float","float4","float8","for","force","foreign","found","free","freetext","freetexttable","from","full","fulltext","function","general","generated","get","get_current_connection","global","go","goto","grant","graphic","group","grouping","handler","having","high_priority","hold","holdlock","hour","hour_microsecond","hour_minute","hour_second","hours","identified","identity","identity_insert","identitycol","if","ignore","immediate","in","including","increment","index","indicator","infile","inherit","initial","initially","inner","inout","input","insensitive","insert","int","int1","int2","int3","int4","int8","integer","integrity","intersect","interval","into","is","isobid","isolation","iterate","jar","java","join","key","keys","kill","language","large","last","lateral","leading","leave","left","level","like","limit","linear","lineno","lines","linktype","load","local","locale","localtime","localtimestamp","locator","locators","lock","lockmax","locksize","long","longblob","longint","longtext","loop","low_priority","lower","ltrim","map","master_ssl_verify_server_cert","match","max","maxextents","maxvalue","mediumblob","mediumint","mediumtext","method","microsecond","microseconds","middleint","min","minus","minute","minute_microsecond","minute_second","minutes","minvalue","mlslabel","mod","mode","modifies","modify","module","month","months","names","national","natural","nchar","nclob","new","new_table","next","no","no_write_to_binlog","noaudit","nocache","nocheck","nocompress","nocycle","nodename","nodenumber","nomaxvalue","nominvalue","nonclustered","none","noorder","not","nowait","null","nullif","nulls","number","numeric","numparts","nvarchar","obid","object","octet_length","of","off","offline","offsets","old","old_table","on","online","only","open","opendatasource","openquery","openrowset","openxml","optimization","optimize","option","optionally","or","order","ordinality","out","outer","outfile","output","over","overlaps","overriding","package","pad","parameter","part","partial","partition","path","pctfree","percent","piecesize","plan","position","precision","prepare","preserve","primary","print","prior","priqty","privileges","proc","procedure","program","psid","public","purge","queryno","raiserror","range","raw","read","read_write","reads","readtext","real","reconfigure","recovery","recursive","ref","references","referencing","regexp","relative","release","rename","repeat","replace","replication","require","resignal","resource","restart","restore","restrict","result","result_set_locator","return","returns","revoke","right","rlike","role","rollback","rollup","routine","row","rowcount","rowguidcol","rowid","rownum","rows","rrn","rtrim","rule","run","runtimestatistics","save","savepoint","schema","schemas","scope","scratchpad","scroll","search","second","second_microsecond","seconds","secqty","section","security","select","sensitive","separator","session","session_user","set","sets","setuser","share","show","shutdown","signal","similar","simple","size","smallint","some","source","space","spatial","specific","specifictype","sql","sql_big_result","sql_calc_found_rows","sql_small_result","sqlcode","sqlerror","sqlexception","sqlid","sqlstate","sqlwarning","ssl","standard","start","starting","state","static","statistics","stay","stogroup","stores","straight_join","style","subpages","substr","substring","successful","sum","symmetric","synonym","sysdate","sysfun","sysibm","sysproc","system","system_user","table","tablespace","temporary","terminated","textsize","then","time","timestamp","timezone_hour","timezone_minute","tinyblob","tinyint","tinytext","to","top","trailing","tran","transaction","translate","translation","treat","trigger","trim","true","truncate","tsequal","type","uid","under","undo","union","unique","unknown","unlock","unnest","unsigned","until","update","updatetext","upper","usage","use","user","using","utc_date","utc_time","utc_timestamp","validate","validproc","value","values","varbinary","varchar","varchar2","varcharacter","variable","variant","varying","vcat","view","volumes","waitfor","when","whenever","where","while","window","with","within","without","wlm","work","write","writetext","xor","year","year_month","zerofill","zone"};
063        private static final Set<String> keywords=new HashSet<String>();
064        static {
065                for(int i=0;i<KEYWORDS.length;i++){
066                        keywords.add(KEYWORDS[i]);
067                }
068        }
069        
070        
071        public static boolean isKeyword(String word){
072                if(word==null) return false;
073                return keywords.contains(word.trim().toLowerCase());
074        }
075        
076        
077        public static Type getPropertyType(ClassMetadata metaData, String name) throws HibernateException {
078                try{
079                        return  metaData.getPropertyType(name);
080                }
081                catch(HibernateException he){
082                        if(name.equalsIgnoreCase(metaData.getIdentifierPropertyName())) 
083                                return metaData.getIdentifierType();
084                        
085                        String[] names = metaData.getPropertyNames();
086                        for(int i=0;i<names.length;i++){
087                                if(names[i].equalsIgnoreCase(name))
088                                        return metaData.getPropertyType(names[i]);
089                        }
090                        throw he;
091                }
092        }
093        public static Type getPropertyType(ClassMetadata metaData, String name, Type defaultValue) {
094                try{
095                        return  metaData.getPropertyType(name);
096                }
097                catch(HibernateException he){
098                        if(name.equalsIgnoreCase(metaData.getIdentifierPropertyName())) 
099                                return metaData.getIdentifierType();
100                        
101                        String[] names = metaData.getPropertyNames();
102                        for(int i=0;i<names.length;i++){
103                                if(names[i].equalsIgnoreCase(name))
104                                        return metaData.getPropertyType(names[i]);
105                        }
106                        return defaultValue;
107                }
108        }
109        
110        public static String validateColumnName(ClassMetadata metaData, String name) throws PageException {
111                String res = validateColumnName(metaData, name,null);
112                if(res!=null) return res;
113                throw ExceptionUtil.createException((ORMSession)null,null,"invalid name, there is no property with name ["+name+"] in the entity ["+metaData.getEntityName()+"]",
114                                "valid properties names are ["+CommonUtil.toList(metaData.getPropertyNames(), ", ")+"]");
115                
116        }
117        
118
119        public static String validateColumnName(ClassMetadata metaData, String name, String defaultValue) {
120                Type type = metaData.getIdentifierType();
121                // composite id
122                if(type.isComponentType()) {
123                        String res=_validateColumnName(((ComponentType) type).getPropertyNames(),name);
124                        if(res!=null) return res;
125                }
126                // regular id
127                String id = metaData.getIdentifierPropertyName();
128                if(id!=null && name.equalsIgnoreCase(id)) 
129                        return metaData.getIdentifierPropertyName();
130                
131                String res=_validateColumnName(metaData.getPropertyNames(),name);
132                if(res!=null) return res;
133                return defaultValue;
134        }
135        
136        private static String _validateColumnName(String[] names, String name) {
137                if(names==null) return null;
138                for(int i=0;i<names.length;i++){
139                        if(names[i].equalsIgnoreCase(name))
140                                return names[i];
141                }
142                return null;
143        }
144
145
146        // 
147        
148        public static Property[] createPropertiesFromTable(DatasourceConnection dc, String tableName) {
149                Struct properties = CommonUtil.createStruct();
150                try {
151                        DatabaseMetaData md = dc.getConnection().getMetaData();
152                        String dbName=DataSourceUtil.getDatabaseName(dc);
153                        Collection.Key name;
154                        
155                        
156                        // get all columns
157                        ResultSet res = md.getColumns(dbName, null, tableName, null);
158                        while(res.next()) {
159                                name=CommonUtil.createKey(res.getString("COLUMN_NAME"));
160                                properties.setEL(
161                                                name, 
162                                                CommonUtil.createProperty(name.getString(),res.getString("TYPE_NAME")));
163                        }
164                        
165                        // ids
166                        res = md.getPrimaryKeys(null, null, tableName);
167                        Property p;
168                        while(res.next()) {
169                                name=CommonUtil.createKey(res.getString("COLUMN_NAME"));
170                                p=(Property) properties.get(name,null);
171                                if(p!=null) p.getDynamicAttributes().setEL(CommonUtil.FIELDTYPE, "id");
172                        }
173                        
174                        // MUST foreign-key relation
175                
176                }
177                catch(Throwable t){
178                        lucee.commons.lang.ExceptionUtil.rethrowIfNecessary(t);
179                        return new Property[0];
180                }
181                
182                Iterator<Object> it = properties.valueIterator();
183                Property[] rtn=new Property[properties.size()];
184                for(int i=0;i<rtn.length;i++){
185                        rtn[i]=(Property) it.next();
186                }
187                
188        return rtn;
189        }
190
191
192        public static Property[] getProperties(Component component,int fieldType, Property[] defaultValue) {
193                Property[] props = component.getProperties(true);
194                java.util.List<Property> rtn=new ArrayList<Property>();
195                
196                if(props!=null) {
197                        for(int i=0;i<props.length;i++){
198                                if(fieldType==getFieldType(props[i],FIELDTYPE_COLUMN))
199                                        rtn.add(props[i]);
200                        }
201                }
202                return rtn.toArray(new Property[rtn.size()]);
203        }
204
205
206        private static int getFieldType(Property property, int defaultValue) {
207                return getFieldType(CommonUtil.toString(property.getDynamicAttributes().get(CommonUtil.FIELDTYPE, null),null),defaultValue);
208                        
209        }
210        
211        private static int getFieldType(String fieldType, int defaultValue) {
212                if(Util.isEmpty(fieldType,true)) return defaultValue;
213                fieldType=fieldType.trim().toLowerCase();
214                
215
216                if("id".equals(fieldType)) return FIELDTYPE_ID;
217                if("column".equals(fieldType)) return FIELDTYPE_COLUMN;
218                if("timestamp".equals(fieldType)) return FIELDTYPE_TIMESTAMP;
219                if("relation".equals(fieldType)) return FIELDTYPE_RELATION;
220                if("version".equals(fieldType)) return FIELDTYPE_VERSION;
221                if("collection".equals(fieldType)) return FIELDTYPE_COLLECTION;
222                return defaultValue;
223        }
224        
225
226
227        public static String convertTableName(SessionFactoryData data,String tableName) throws PageException {
228                if(tableName==null) return null;
229                return data.getNamingStrategy().convertTableName(tableName);
230        }
231
232        public static String convertColumnName(SessionFactoryData data,String columnName) throws PageException {
233                if(columnName==null) return null;
234                return data.getNamingStrategy().convertColumnName(columnName);
235        }
236        
237        public static boolean isEntity(ORMConfiguration ormConf,Component cfc, String cfcName, String name) {
238                if(!Util.isEmpty(cfcName)) {
239                        if(cfc.equalTo(cfcName)) return true;
240
241                        if(cfcName.indexOf('.')!=-1) {
242                                String path=cfcName.replace('.', '/')+".cfc";
243                                Resource[] locations = ormConf.getCfcLocations();
244                                for(int i=0;i<locations.length;i++){
245                                        if(locations[i].getRealResource(path).equals(cfc.getPageSource().getResource()))
246                                                return true;
247                                }
248                                return false;
249                        }
250                }
251                
252                if(cfc.equalTo(name)) return true;
253                return name.equalsIgnoreCase(HibernateCaster.getEntityName(cfc));
254        }
255        
256        public static String id(String id) {
257                return id.toLowerCase().trim();
258        }
259        
260        public static Struct checkTable(DatasourceConnection dc, String tableName, SessionFactoryData data) throws PageException {
261                
262                try {
263                        String dbName=DataSourceUtil.getDatabaseName(dc);
264                        DatabaseMetaData md = dc.getConnection().getMetaData();
265                        Struct rows=checkTableFill(md,dbName,tableName);
266                        if(rows.size()==0)      {
267                                String tableName2 = checkTableValidate(md,dbName,tableName);
268                                if(tableName2!=null)rows=checkTableFill(md,dbName,tableName2);
269                        }
270                        
271                        if(rows.size()==0)      {
272                                return null;
273                        }
274                        return rows;
275                } catch (SQLException e) {
276                        throw CommonUtil.toPageException(e);
277                }
278        }
279        
280
281
282        private static Struct checkTableFill(DatabaseMetaData md, String dbName, String tableName) throws SQLException, PageException {
283                Struct rows=new CastableStruct(tableName,Struct.TYPE_LINKED);
284                ResultSet columns = md.getColumns(dbName, null, tableName, null);
285                //print.o(new QueryImpl(columns,""));
286                try{
287                        String name;
288                        Object nullable;
289                        while(columns.next()) {
290                                name=columns.getString("COLUMN_NAME");
291                                
292                                nullable=columns.getObject("IS_NULLABLE");
293                                rows.setEL(CommonUtil.createKey(name),new ColumnInfo(
294                                                name,
295                                                columns.getInt("DATA_TYPE"),
296                                                columns.getString("TYPE_NAME"),
297                                                columns.getInt("COLUMN_SIZE"),
298                                                CommonUtil.toBooleanValue(nullable)     
299                                ));
300                        }
301                }
302                finally {
303                        CommonUtil.closeEL(columns);
304                }// Table susid defined for cfc susid does not exist.
305                
306                return rows;
307        }
308
309        private static String checkTableValidate(DatabaseMetaData md, String dbName,String tableName) {
310
311                ResultSet tables=null;
312        try{
313                tables = md.getTables(dbName, null, null, null);
314                        String name;
315                        while(tables.next()) {
316                                name=tables.getString("TABLE_NAME");
317                                if(name.equalsIgnoreCase(tableName) && tables.getString("TABLE_TYPE").toUpperCase().indexOf("SYSTEM")==-1)
318                                return name;    
319                        }
320                }
321        catch(Throwable t){
322                        lucee.commons.lang.ExceptionUtil.rethrowIfNecessary(t);
323                }
324                finally {
325                        CommonUtil.closeEL(tables);
326                }
327        return null;
328        
329        
330                
331        }
332
333
334        public static HibernateORMEngine getORMEngine(PageContext pc) throws PageException {
335                if(pc==null) pc=CommonUtil.pc();
336                ConfigImpl config = (ConfigImpl) pc.getConfig();
337                return (HibernateORMEngine) config.getORMEngine(pc);// TODO add this method to the public interface
338        }
339
340
341        public static HibernateORMSession getORMSession(PageContext pc, boolean create) throws PageException {
342                return (HibernateORMSession) ((PageContextImpl)pc).getORMSession(create);// TODO add this method to the public interface
343        }
344        
345
346        
347        public static Property[] getIDProperties(Component c,boolean onlyPeristent, boolean includeBaseProperties) {
348                Property[] props = CommonUtil.getProperties(c,onlyPeristent,includeBaseProperties,false,false);
349                java.util.List<Property> tmp=new ArrayList<Property>();
350                for(int i=0;i<props.length;i++){
351                        if("id".equalsIgnoreCase(CommonUtil.toString(props[i].getDynamicAttributes().get(CommonUtil.FIELDTYPE,null),"")))
352                                tmp.add(props[i]);
353                }
354                return tmp.toArray(new Property[tmp.size()]);
355        }
356        
357        public static long getCompileTime(PageContext pc, PageSource ps) throws PageException {
358                return ComponentUtil.getCompileTime(pc, ps);
359        }
360        
361        public static Object getMetaStructItem(Component cfc,Collection.Key name) {
362                return CommonUtil.getMetaStructItem(cfc,name);
363        }
364}