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.io.BufferedReader; 022import java.io.IOException; 023import java.nio.charset.Charset; 024import java.sql.SQLException; 025import java.sql.Statement; 026import java.util.ArrayList; 027import java.util.HashMap; 028import java.util.HashSet; 029import java.util.Iterator; 030import java.util.List; 031import java.util.Map; 032import java.util.Map.Entry; 033import java.util.Set; 034 035import lucee.commons.io.log.Log; 036import lucee.commons.io.log.LogUtil; 037import lucee.commons.io.res.Resource; 038import lucee.commons.io.res.filter.ExtensionResourceFilter; 039import lucee.commons.io.res.util.ResourceUtil; 040import lucee.commons.lang.StringUtil; 041import lucee.commons.sql.SQLUtil; 042import lucee.loader.util.Util; 043import lucee.runtime.Component; 044import lucee.runtime.InterfacePage; 045import lucee.runtime.Mapping; 046import lucee.runtime.Page; 047import lucee.runtime.PageContext; 048import lucee.runtime.PageSource; 049import lucee.runtime.component.ComponentLoader; 050import lucee.runtime.config.Config; 051import lucee.runtime.config.Constants; 052import lucee.runtime.db.DataSource; 053import lucee.runtime.db.DatasourceConnection; 054import lucee.runtime.exp.PageException; 055import lucee.runtime.listener.ApplicationContext; 056import lucee.runtime.orm.ORMConfiguration; 057import lucee.runtime.reflection.Reflector; 058import lucee.runtime.type.Collection.Key; 059 060import org.hibernate.MappingException; 061import org.hibernate.cache.RegionFactory; 062import org.hibernate.cfg.Configuration; 063import org.hibernate.tool.hbm2ddl.SchemaExport; 064import org.hibernate.tool.hbm2ddl.SchemaUpdate; 065import org.w3c.dom.Document; 066 067 068public class HibernateSessionFactory { 069 070 071 public static final String HIBERNATE_3_PUBLIC_ID = "-//Hibernate/Hibernate Mapping DTD 3.0//EN"; 072 public static final String HIBERNATE_3_SYSTEM_ID = "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"; 073 public static final Charset HIBERNATE_3_CHARSET = CommonUtil.UTF8; 074 public static final String HIBERNATE_3_DOCTYPE_DEFINITION = "<!DOCTYPE hibernate-mapping PUBLIC \""+HIBERNATE_3_PUBLIC_ID+"\" \""+HIBERNATE_3_SYSTEM_ID+"\">"; 075 076 077 public static Configuration createConfiguration(Log log,String mappings, DatasourceConnection dc, SessionFactoryData data) throws SQLException, IOException, PageException { 078 /* 079 autogenmap 080 cacheconfig 081 cacheprovider 082 cfclocation 083 datasource 084 dbcreate 085 eventHandling 086 flushatrequestend 087 ormconfig 088 sqlscript 089 useDBForMapping 090 */ 091 092 ORMConfiguration ormConf = data.getORMConfiguration(); 093 094 // dialect 095 DataSource ds = dc.getDatasource(); 096 String dialect=null; 097 try { 098 if (Class.forName(ormConf.getDialect()) != null) { 099 dialect = ormConf.getDialect(); 100 } 101 } 102 catch (Exception e) { 103 // MZ: The dialect value could not be bound to a classname or instantiation causes an exception - ignore and use the default dialect entries 104 } 105 if (dialect == null) { 106 dialect = Dialect.getDialect(ormConf.getDialect()); 107 if(Util.isEmpty(dialect)) dialect=Dialect.getDialect(ds); 108 } 109 if(Util.isEmpty(dialect)) 110 throw ExceptionUtil.createException(data,null,"A valid dialect definition inside the "+Constants.APP_CFC+"/"+Constants.CFAPP_NAME+" is missing. The dialect cannot be determinated automatically",null); 111 112 // Cache Provider 113 String cacheProvider = ormConf.getCacheProvider(); 114 Class<? extends RegionFactory> regionFactory=null; 115 116 if(Util.isEmpty(cacheProvider) || "EHCache".equalsIgnoreCase(cacheProvider)) { 117 //regionFactory=net.sf.ehcache.hibernate.EhCacheRegionFactory.class; 118 regionFactory=net.sf.ehcache.hibernate.SingletonEhCacheRegionFactory.class; 119 cacheProvider=regionFactory.getName();//"org.hibernate.cache.EhCacheProvider"; 120 121 } 122 else if("JBossCache".equalsIgnoreCase(cacheProvider)) cacheProvider="org.hibernate.cache.TreeCacheProvider"; 123 else if("HashTable".equalsIgnoreCase(cacheProvider)) cacheProvider="org.hibernate.cache.HashtableCacheProvider"; 124 else if("SwarmCache".equalsIgnoreCase(cacheProvider)) cacheProvider="org.hibernate.cache.SwarmCacheProvider"; 125 else if("OSCache".equalsIgnoreCase(cacheProvider)) cacheProvider="org.hibernate.cache.OSCacheProvider"; 126 127 Resource cacheConfig = ormConf.getCacheConfig(); 128 Configuration configuration = new Configuration(); 129 130 // ormConfig 131 Resource conf = ormConf.getOrmConfig(); 132 if(conf!=null){ 133 try { 134 Document doc = CommonUtil.toDocument(conf,null); 135 configuration.configure(doc); 136 } 137 catch (Throwable t) { 138 lucee.commons.lang.ExceptionUtil.rethrowIfNecessary(t); 139 LogUtil.log(log, Log.LEVEL_ERROR, "hibernate", t); 140 141 } 142 } 143 144 try{ 145 configuration.addXML(mappings); 146 } 147 catch(MappingException me){ 148 throw ExceptionUtil.createException(data,null, me); 149 } 150 151 configuration 152 153 // Database connection settings 154 .setProperty("hibernate.connection.driver_class", ds.getClazz().getName()) 155 .setProperty("hibernate.connection.url", ds.getDsnTranslated()); 156 if(!StringUtil.isEmpty(ds.getUsername())) { 157 configuration.setProperty("hibernate.connection.username", ds.getUsername()); 158 if(!StringUtil.isEmpty(ds.getPassword())) 159 configuration.setProperty("hibernate.connection.password", ds.getPassword()); 160 } 161 //.setProperty("hibernate.connection.release_mode", "after_transaction") 162 configuration.setProperty("hibernate.transaction.flush_before_completion", "false") 163 .setProperty("hibernate.transaction.auto_close_session", "false") 164 165 // JDBC connection pool (use the built-in) 166 //.setProperty("hibernate.connection.pool_size", "2")//MUST 167 168 169 // SQL dialect 170 .setProperty("hibernate.dialect", dialect) 171 // Enable Hibernate's current session context 172 .setProperty("hibernate.current_session_context_class", "thread") 173 174 // Echo all executed SQL to stdout 175 .setProperty("hibernate.show_sql", CommonUtil.toString(ormConf.logSQL())) 176 .setProperty("hibernate.format_sql", CommonUtil.toString(ormConf.logSQL())) 177 // Specifies whether secondary caching should be enabled 178 .setProperty("hibernate.cache.use_second_level_cache", CommonUtil.toString(ormConf.secondaryCacheEnabled())) 179 // Drop and re-create the database schema on startup 180 .setProperty("hibernate.exposeTransactionAwareSessionFactory", "false") 181 //.setProperty("hibernate.hbm2ddl.auto", "create") 182 .setProperty("hibernate.default_entity_mode", "dynamic-map"); 183 184 if(!Util.isEmpty(ormConf.getCatalog())) 185 configuration.setProperty("hibernate.default_catalog", ormConf.getCatalog()); 186 if(!Util.isEmpty(ormConf.getSchema())) 187 configuration.setProperty("hibernate.default_schema",ormConf.getSchema()); 188 189 190 if(ormConf.secondaryCacheEnabled()){ 191 if(cacheConfig!=null && cacheConfig.isFile()) 192 configuration.setProperty("hibernate.cache.provider_configuration_file_resource_path",cacheConfig.getAbsolutePath()); 193 if(regionFactory!=null || Reflector.isInstaneOf(cacheProvider, RegionFactory.class)) 194 configuration.setProperty("hibernate.cache.region.factory_class", cacheProvider); 195 else 196 configuration.setProperty("hibernate.cache.provider_class", cacheProvider); 197 198 configuration.setProperty("hibernate.cache.use_query_cache", "true"); 199 200 //hibernate.cache.provider_class=org.hibernate.cache.EhCacheProvider 201 } 202 203 /* 204 <!ELEMENT tuplizer EMPTY> 205 <!ATTLIST tuplizer entity-mode (pojo|dom4j|dynamic-map) #IMPLIED> <!-- entity mode for which tuplizer is in effect --> 206 <!ATTLIST tuplizer class CDATA #REQUIRED> <!-- the tuplizer class to use --> 207 */ 208 209 schemaExport(log,configuration,dc,data); 210 211 return configuration; 212 } 213 214 private static void schemaExport(Log log,Configuration configuration, DatasourceConnection dc, SessionFactoryData data) throws PageException, SQLException, IOException { 215 ORMConfiguration ormConf = data.getORMConfiguration(); 216 217 if(ORMConfiguration.DBCREATE_NONE==ormConf.getDbCreate()) { 218 return; 219 } 220 else if(ORMConfiguration.DBCREATE_DROP_CREATE==ormConf.getDbCreate()) { 221 SchemaExport export = new SchemaExport(configuration); 222 export.setHaltOnError(true); 223 224 export.execute(false,true,false,false); 225 printError(log,data,export.getExceptions(),false); 226 executeSQLScript(ormConf,dc); 227 } 228 else if(ORMConfiguration.DBCREATE_UPDATE==ormConf.getDbCreate()) { 229 SchemaUpdate update = new SchemaUpdate(configuration); 230 update.setHaltOnError(true); 231 update.execute(false, true); 232 printError(log,data,update.getExceptions(),false); 233 } 234 } 235 236 private static void printError(Log log,SessionFactoryData data, List<Exception> exceptions,boolean throwException) throws PageException { 237 if(exceptions==null || exceptions.size()==0) return; 238 Iterator<Exception> it = exceptions.iterator(); 239 if(!throwException || exceptions.size()>1){ 240 while(it.hasNext()) { 241 LogUtil.log(log, Log.LEVEL_ERROR, "hibernate", it.next()); 242 } 243 } 244 if(!throwException) return; 245 246 it = exceptions.iterator(); 247 while(it.hasNext()) { 248 throw ExceptionUtil.createException(data,null,it.next()); 249 } 250 } 251 252 private static void executeSQLScript(ORMConfiguration ormConf,DatasourceConnection dc) throws SQLException, IOException { 253 Resource sqlScript = ormConf.getSqlScript(); 254 if(sqlScript!=null && sqlScript.isFile()) { 255 BufferedReader br = CommonUtil.toBufferedReader(sqlScript,(Charset)null); 256 String line; 257 StringBuilder sql=new StringBuilder(); 258 String str; 259 Statement stat = dc.getConnection().createStatement(); 260 try{ 261 while((line=br.readLine())!=null){ 262 line=line.trim(); 263 if(line.startsWith("//") || line.startsWith("--")) continue; 264 if(line.endsWith(";")){ 265 sql.append(line.substring(0,line.length()-1)); 266 str=sql.toString().trim(); 267 if(str.length()>0)stat.execute(str); 268 sql=new StringBuilder(); 269 } 270 else { 271 sql.append(line).append(" "); 272 } 273 } 274 str=sql.toString().trim(); 275 if(str.length()>0){ 276 stat.execute(str); 277 } 278 } 279 finally { 280 SQLUtil.closeEL(stat); 281 } 282 } 283 } 284 285 286 public static Map<Key,String> createMappings(ORMConfiguration ormConf, SessionFactoryData data) { 287 Map<Key,String> mappings=new HashMap<Key,String>(); 288 Iterator<Entry<Key, Map<String, CFCInfo>>> it = data.getCFCs().entrySet().iterator(); 289 while(it.hasNext()){ 290 Entry<Key, Map<String, CFCInfo>> e = it.next(); 291 292 Set<String> done=new HashSet<String>(); 293 StringBuilder mapping=new StringBuilder(); 294 mapping.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); 295 mapping.append(HIBERNATE_3_DOCTYPE_DEFINITION+"\n"); 296 mapping.append("<hibernate-mapping>\n"); 297 Iterator<Entry<String, CFCInfo>> _it = e.getValue().entrySet().iterator(); 298 Entry<String, CFCInfo> entry; 299 while(_it.hasNext()){ 300 entry = _it.next(); 301 createMappings(ormConf,entry.getKey(),entry.getValue(),done,mapping,data); 302 303 } 304 mapping.append("</hibernate-mapping>"); 305 mappings.put(e.getKey(), mapping.toString()); 306 } 307 return mappings; 308 } 309 310 private static void createMappings(ORMConfiguration ormConf, String key, CFCInfo value,Set<String> done,StringBuilder mappings, SessionFactoryData data) { 311 if(done.contains(key)) return; 312 CFCInfo v; 313 String ext = value.getCFC().getExtends(); 314 if(!Util.isEmpty(ext)){ 315 try { 316 Component base = data.getEntityByCFCName(ext, false); 317 ext=HibernateCaster.getEntityName(base); 318 } catch (Throwable t) { 319 lucee.commons.lang.ExceptionUtil.rethrowIfNecessary(t); 320 } 321 322 323 ext=HibernateUtil.id(CommonUtil.last(ext, '.').trim()); 324 if(!done.contains(ext)) { 325 v = data.getCFC(ext,null); 326 if(v!=null)createMappings(ormConf, ext, v, done, mappings,data); 327 } 328 } 329 330 mappings.append(value.getXML()); 331 done.add(key); 332 } 333 334 public static List<Component> loadComponents(PageContext pc,HibernateORMEngine engine, ORMConfiguration ormConf) throws PageException { 335 ExtensionResourceFilter filter = new ExtensionResourceFilter(pc.getConfig().getCFCExtension(),true); 336 List<Component> components=new ArrayList<Component>(); 337 loadComponents(pc,engine,components,ormConf.getCfcLocations(),filter,ormConf); 338 return components; 339 } 340 341 private static void loadComponents(PageContext pc, HibernateORMEngine engine,List<Component> components,Resource[] reses,ExtensionResourceFilter filter,ORMConfiguration ormConf) throws PageException { 342 Mapping[] mappings = createMappings(pc, reses); 343 ApplicationContext ac=pc.getApplicationContext(); 344 Mapping[] existing = ac.getComponentMappings(); 345 if(existing==null) existing=new Mapping[0]; 346 try{ 347 Mapping[] tmp = new Mapping[existing.length+1]; 348 for(int i=1;i<tmp.length;i++){ 349 tmp[i]=existing[i-1]; 350 } 351 ac.setComponentMappings(tmp); 352 for(int i=0;i<reses.length;i++){ 353 if(reses[i]!=null && reses[i].isDirectory()){ 354 tmp[0] = mappings[i]; 355 ac.setComponentMappings(tmp); 356 loadComponents(pc,engine,mappings[i],components,reses[i], filter,ormConf); 357 } 358 } 359 } 360 finally { 361 ac.setComponentMappings(existing); 362 } 363 } 364 365 private static void loadComponents(PageContext pc, HibernateORMEngine engine,Mapping cfclocation,List<Component> components,Resource res,ExtensionResourceFilter filter,ORMConfiguration ormConf) throws PageException { 366 if(res==null) return; 367 368 if(res.isDirectory()){ 369 Resource[] children = res.listResources(filter); 370 371 // first load all files 372 for(int i=0;i<children.length;i++){ 373 if(children[i].isFile())loadComponents(pc,engine,cfclocation,components,children[i], filter,ormConf); 374 } 375 376 // and then invoke subfiles 377 for(int i=0;i<children.length;i++){ 378 if(children[i].isDirectory())loadComponents(pc,engine,cfclocation,components,children[i], filter,ormConf); 379 } 380 } 381 else if(res.isFile()){ 382 if(!res.getName().equalsIgnoreCase(Constants.APP_CFC)) { 383 try { 384 385 // MUST still a bad solution 386 PageSource ps = pc.toPageSource(res,null); 387 if(ps==null || ps.getComponentName().indexOf("..")!=-1) { 388 PageSource ps2=null; 389 Resource root = cfclocation.getPhysical(); 390 String path = ResourceUtil.getPathToChild(res, root); 391 if(!Util.isEmpty(path,true)) { 392 ps2=cfclocation.getPageSource(path); 393 } 394 if(ps2!=null)ps=ps2; 395 } 396 397 398 //Page p = ps.loadPage(pc.getConfig()); 399 String name=res.getName(); 400 name=name.substring(0,name.length()-4); 401 Page p = ComponentLoader.loadPage(pc, ps,true); 402 if(!(p instanceof InterfacePage)){ 403 Component cfc = ComponentLoader.loadComponent(pc, p, ps, name, true,true); 404 if(CommonUtil.isPersistent(cfc)){ 405 components.add(cfc); 406 } 407 } 408 } 409 catch (PageException e) { 410 if(!ormConf.skipCFCWithError())throw e; 411 //e.printStackTrace(); 412 } 413 } 414 } 415 } 416 417 418 public static Mapping[] createMappings(PageContext pc,Resource[] resources) { 419 420 Mapping[] mappings=new Mapping[resources.length]; 421 Config config=pc.getConfig(); 422 for(int i=0;i<mappings.length;i++) { 423 mappings[i]=CommonUtil.createMapping(config, 424 "/", 425 resources[i].getAbsolutePath() 426 ); 427 } 428 return mappings; 429 } 430}