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.util.ArrayList; 022import java.util.HashMap; 023import java.util.Iterator; 024import java.util.List; 025import java.util.Map; 026import java.util.Map.Entry; 027import java.util.concurrent.ConcurrentHashMap; 028 029import lucee.commons.io.log.Log; 030import lucee.commons.io.res.Resource; 031import lucee.loader.util.Util; 032import lucee.runtime.Component; 033import lucee.runtime.PageContext; 034import lucee.runtime.config.ConfigImpl; 035import lucee.runtime.db.DataSource; 036import lucee.runtime.db.DataSourceManager; 037import lucee.runtime.db.DatasourceConnection; 038import lucee.runtime.exp.PageException; 039import lucee.runtime.listener.ApplicationContext; 040import lucee.runtime.listener.ApplicationContextPro; 041import lucee.runtime.orm.ORMConfiguration; 042import lucee.runtime.orm.ORMEngine; 043import lucee.runtime.orm.ORMSession; 044import lucee.runtime.orm.ORMUtil; 045import lucee.runtime.orm.hibernate.event.AllEventListener; 046import lucee.runtime.orm.hibernate.event.EventListener; 047import lucee.runtime.orm.hibernate.event.InterceptorImpl; 048import lucee.runtime.orm.hibernate.event.PostDeleteEventListenerImpl; 049import lucee.runtime.orm.hibernate.event.PostInsertEventListenerImpl; 050import lucee.runtime.orm.hibernate.event.PostLoadEventListenerImpl; 051import lucee.runtime.orm.hibernate.event.PostUpdateEventListenerImpl; 052import lucee.runtime.orm.hibernate.event.PreDeleteEventListenerImpl; 053import lucee.runtime.orm.hibernate.event.PreInsertEventListenerImpl; 054import lucee.runtime.orm.hibernate.event.PreLoadEventListenerImpl; 055import lucee.runtime.orm.hibernate.event.PreUpdateEventListenerImpl; 056import lucee.runtime.orm.hibernate.tuplizer.AbstractEntityTuplizerImpl; 057import lucee.runtime.text.xml.XMLCaster; 058import lucee.runtime.type.Collection; 059import lucee.runtime.type.Collection.Key; 060import lucee.runtime.type.util.ListUtil; 061 062import org.hibernate.EntityMode; 063import org.hibernate.SessionFactory; 064import org.hibernate.cfg.Configuration; 065import org.hibernate.event.EventListeners; 066import org.hibernate.event.PostDeleteEventListener; 067import org.hibernate.event.PostInsertEventListener; 068import org.hibernate.event.PostLoadEventListener; 069import org.hibernate.event.PostUpdateEventListener; 070import org.hibernate.event.PreDeleteEventListener; 071import org.hibernate.event.PreLoadEventListener; 072import org.hibernate.tuple.entity.EntityTuplizerFactory; 073import org.w3c.dom.Document; 074import org.w3c.dom.Element; 075 076public class HibernateORMEngine implements ORMEngine { 077 078 private static final int INIT_NOTHING=1; 079 private static final int INIT_CFCS=2; 080 private static final int INIT_ALL=2; 081 082 private Map<String,SessionFactoryData> factories=new ConcurrentHashMap<String, SessionFactoryData>(); 083 084 public HibernateORMEngine() {} 085 086 @Override 087 public void init(PageContext pc) throws PageException{ 088 SessionFactoryData data = getSessionFactoryData(pc, INIT_CFCS); 089 data.init();// init all factories 090 } 091 092 @Override 093 public ORMSession createSession(PageContext pc) throws PageException { 094 try{ 095 SessionFactoryData data = getSessionFactoryData(pc, INIT_NOTHING); 096 return new HibernateORMSession(pc,data); 097 } 098 catch(PageException pe){ 099 throw pe; 100 } 101 } 102 103 104 /*QueryPlanCache getQueryPlanCache(PageContext pc) throws PageException { 105 return getSessionFactoryData(pc,INIT_NOTHING).getQueryPlanCache(); 106 }*/ 107 108 /*public SessionFactory getSessionFactory(PageContext pc) throws PageException{ 109 return getSessionFactory(pc,INIT_NOTHING); 110 }*/ 111 112 public boolean reload(PageContext pc, boolean force) throws PageException { 113 if(force) { 114 getSessionFactoryData(pc, INIT_ALL); 115 } 116 else { 117 if(factories.containsKey(hash(pc)))return false; 118 } 119 getSessionFactoryData(pc, INIT_CFCS); 120 return true; 121 } 122 123 private SessionFactoryData getSessionFactoryData(PageContext pc,int initType) throws PageException { 124 ApplicationContextPro appContext = (ApplicationContextPro) pc.getApplicationContext(); 125 if(!appContext.isORMEnabled()) 126 throw ExceptionUtil.createException((ORMSession)null,null,"ORM is not enabled",""); 127 128 129 // datasource 130 ORMConfiguration ormConf=appContext.getORMConfiguration(); 131 String key = hash(ormConf); 132 SessionFactoryData data = factories.get(key); 133 if(initType==INIT_ALL && data!=null) { 134 data.reset(); 135 data=null; 136 } 137 if(data==null) { 138 data=new SessionFactoryData(this,ormConf); 139 factories.put(key, data); 140 } 141 142 143 // config 144 try{ 145 //arr=null; 146 if(initType!=INIT_NOTHING){ 147 synchronized (data) { 148 149 if(ormConf.autogenmap()){ 150 data.tmpList=HibernateSessionFactory.loadComponents(pc, this, ormConf); 151 152 data.clearCFCs(); 153 } 154 else 155 throw ExceptionUtil.createException(data,null,"orm setting autogenmap=false is not supported yet",null); 156 157 // load entities 158 if(data.tmpList!=null && data.tmpList.size()>0) { 159 data.getNamingStrategy();// called here to make sure, it is called in the right context the first one 160 161 // creates CFCInfo objects 162 { 163 Iterator<Component> it = data.tmpList.iterator(); 164 while(it.hasNext()){ 165 createMapping(pc,it.next(),ormConf,data); 166 } 167 } 168 169 if(data.tmpList.size()!=data.sizeCFCs()){ 170 Component cfc; 171 String name,lcName; 172 Map<String,String> names=new HashMap<String,String>(); 173 Iterator<Component> it = data.tmpList.iterator(); 174 while(it.hasNext()){ 175 cfc=it.next(); 176 name=HibernateCaster.getEntityName(cfc); 177 lcName=name.toLowerCase(); 178 if(names.containsKey(lcName)) 179 throw ExceptionUtil.createException(data,null,"Entity Name ["+name+"] is ambigous, ["+names.get(lcName)+"] and ["+cfc.getPageSource().getDisplayPath()+"] use the same entity name.",""); 180 names.put(lcName,cfc.getPageSource().getDisplayPath()); 181 } 182 } 183 } 184 } 185 } 186 } 187 finally { 188 data.tmpList=null; 189 } 190 191 // already initialized for this application context 192 193 //MUST 194 //cacheconfig 195 //cacheprovider 196 //... 197 198 Log log = ((ConfigImpl)pc.getConfig()).getLog("orm"); 199 200 Iterator<Entry<Key, String>> it = HibernateSessionFactory.createMappings(ormConf,data).entrySet().iterator(); 201 Entry<Key, String> e; 202 while(it.hasNext()) { 203 e = it.next(); 204 if(data.getConfiguration(e.getKey())!=null) continue; 205 206 DatasourceConnection dc = CommonUtil.getDatasourceConnection(pc,data.getDataSource(e.getKey())); 207 try{ 208 data.setConfiguration(log,e.getValue(),dc); 209 } 210 catch (Exception ex) { 211 throw CommonUtil.toPageException(ex); 212 } 213 finally { 214 CommonUtil.releaseDatasourceConnection(pc, dc); 215 } 216 addEventListeners(pc, data,e.getKey()); 217 218 EntityTuplizerFactory tuplizerFactory = data.getConfiguration(e.getKey()).getEntityTuplizerFactory(); 219 tuplizerFactory.registerDefaultTuplizerClass(EntityMode.MAP, AbstractEntityTuplizerImpl.class); 220 tuplizerFactory.registerDefaultTuplizerClass(EntityMode.POJO, AbstractEntityTuplizerImpl.class); 221 222 data.buildSessionFactory(e.getKey()); 223 } 224 225 return data; 226 } 227 228 private static void addEventListeners(PageContext pc, SessionFactoryData data, Key key) throws PageException { 229 if(!data.getORMConfiguration().eventHandling()) return; 230 String eventHandler = data.getORMConfiguration().eventHandler(); 231 AllEventListener listener=null; 232 if(!Util.isEmpty(eventHandler,true)){ 233 //try { 234 Component c = pc.loadComponent(eventHandler.trim()); 235 236 listener = new AllEventListener(c); 237 //config.setInterceptor(listener); 238 //}catch (PageException e) {e.printStackTrace();} 239 } 240 Configuration conf = data.getConfiguration(key); 241 conf.setInterceptor(new InterceptorImpl(listener)); 242 EventListeners listeners = conf.getEventListeners(); 243 Map<String, CFCInfo> cfcs = data.getCFCs(key); 244 // post delete 245 List<EventListener> 246 list=merge(listener,cfcs,CommonUtil.POST_DELETE); 247 listeners.setPostDeleteEventListeners(list.toArray(new PostDeleteEventListener[list.size()])); 248 249 // post insert 250 list=merge(listener,cfcs,CommonUtil.POST_INSERT); 251 listeners.setPostInsertEventListeners(list.toArray(new PostInsertEventListener[list.size()])); 252 253 // post update 254 list=merge(listener,cfcs,CommonUtil.POST_UPDATE); 255 listeners.setPostUpdateEventListeners(list.toArray(new PostUpdateEventListener[list.size()])); 256 257 // post load 258 list=merge(listener,cfcs,CommonUtil.POST_LOAD); 259 listeners.setPostLoadEventListeners(list.toArray(new PostLoadEventListener[list.size()])); 260 261 // pre delete 262 list=merge(listener,cfcs,CommonUtil.PRE_DELETE); 263 listeners.setPreDeleteEventListeners(list.toArray(new PreDeleteEventListener[list.size()])); 264 265 // pre insert 266 //list=merge(listener,cfcs,CommonUtil.PRE_INSERT); 267 //listeners.setPreInsertEventListeners(list.toArray(new PreInsertEventListener[list.size()])); 268 269 // pre load 270 list=merge(listener,cfcs,CommonUtil.PRE_LOAD); 271 listeners.setPreLoadEventListeners(list.toArray(new PreLoadEventListener[list.size()])); 272 273 // pre update 274 //list=merge(listener,cfcs,CommonUtil.PRE_UPDATE); 275 //listeners.setPreUpdateEventListeners(list.toArray(new PreUpdateEventListener[list.size()])); 276 } 277 278 private static List<EventListener> merge(EventListener listener, Map<String, CFCInfo> cfcs, Collection.Key eventType) { 279 List<EventListener> list=new ArrayList<EventListener>(); 280 281 282 Iterator<Entry<String, CFCInfo>> it = cfcs.entrySet().iterator(); 283 Entry<String, CFCInfo> entry; 284 Component cfc; 285 while(it.hasNext()){ 286 entry = it.next(); 287 cfc = entry.getValue().getCFC(); 288 if(EventListener.hasEventType(cfc,eventType)) { 289 if(CommonUtil.POST_DELETE.equals(eventType)) 290 list.add(new PostDeleteEventListenerImpl(cfc)); 291 if(CommonUtil.POST_INSERT.equals(eventType)) 292 list.add(new PostInsertEventListenerImpl(cfc)); 293 if(CommonUtil.POST_LOAD.equals(eventType)) 294 list.add(new PostLoadEventListenerImpl(cfc)); 295 if(CommonUtil.POST_UPDATE.equals(eventType)) 296 list.add(new PostUpdateEventListenerImpl(cfc)); 297 298 if(CommonUtil.PRE_DELETE.equals(eventType)) 299 list.add(new PreDeleteEventListenerImpl(cfc)); 300 if(CommonUtil.PRE_INSERT.equals(eventType)) 301 list.add(new PreInsertEventListenerImpl(cfc)); 302 if(CommonUtil.PRE_LOAD.equals(eventType)) 303 list.add(new PreLoadEventListenerImpl(cfc)); 304 if(CommonUtil.PRE_UPDATE.equals(eventType)) 305 list.add(new PreUpdateEventListenerImpl(cfc)); 306 } 307 } 308 309 // general listener 310 if(listener!=null && EventListener.hasEventType(listener.getCFC(),eventType)) 311 list.add(listener); 312 313 return list; 314 } 315 316 private static Object hash(PageContext pc) { 317 ApplicationContextPro appContext=(ApplicationContextPro) pc.getApplicationContext(); 318 return hash(appContext.getORMConfiguration()); 319 } 320 321 private static String hash(ORMConfiguration ormConf) { 322 return ormConf.hash(); 323 } 324 325 public void createMapping(PageContext pc,Component cfc, ORMConfiguration ormConf,SessionFactoryData data) throws PageException { 326 String entityName=HibernateCaster.getEntityName(cfc); 327 CFCInfo info=data.getCFC(entityName,null); 328 String xml; 329 long cfcCompTime = HibernateUtil.getCompileTime(pc,cfc.getPageSource()); 330 if(info==null || (ORMUtil.equals(info.getCFC(),cfc) )) {//&& info.getModified()!=cfcCompTime 331 DataSource ds = ORMUtil.getDataSource(pc,cfc); 332 StringBuilder sb=new StringBuilder(); 333 334 long xmlLastMod = loadMapping(sb,ormConf, cfc); 335 Element root; 336 // create mapping 337 if(true || xmlLastMod< cfcCompTime) {//MUSTMUST 338 data.reset(); 339 Document doc=null; 340 try { 341 doc=CommonUtil.newDocument(); 342 }catch(Throwable t){ 343 lucee.commons.lang.ExceptionUtil.rethrowIfNecessary(t); 344 t.printStackTrace(); 345 } 346 347 root=doc.createElement("hibernate-mapping"); 348 doc.appendChild(root); 349 pc.addPageSource(cfc.getPageSource(), true); 350 DataSourceManager manager = pc.getDataSourceManager(); 351 DatasourceConnection dc = manager.getConnection(pc, ds, null, null); 352 try{ 353 HBMCreator.createXMLMapping(pc,dc,cfc,root,data); 354 } 355 finally{ 356 pc.removeLastPageSource(true); 357 manager.releaseConnection(pc, dc); 358 } 359 xml=XMLCaster.toString(root.getChildNodes(),true,true); 360 saveMapping(ormConf,cfc,root); 361 } 362 // load 363 else { 364 xml=sb.toString(); 365 root=CommonUtil.toXML(xml).getOwnerDocument().getDocumentElement(); 366 /*print.o("1+++++++++++++++++++++++++++++++++++++++++"); 367 print.o(xml); 368 print.o("2+++++++++++++++++++++++++++++++++++++++++"); 369 print.o(root); 370 print.o("3+++++++++++++++++++++++++++++++++++++++++");*/ 371 372 } 373 data.addCFC(entityName,new CFCInfo(HibernateUtil.getCompileTime(pc,cfc.getPageSource()),xml,cfc,ds)); 374 } 375 376 } 377 378 private static void saveMapping(ORMConfiguration ormConf, Component cfc, Element hm) { 379 if(ormConf.saveMapping()){ 380 Resource res=cfc.getPageSource().getResource(); 381 if(res!=null){ 382 res=res.getParentResource().getRealResource(res.getName()+".hbm.xml"); 383 try{ 384 CommonUtil.write(res, 385 XMLCaster.toString(hm,false,true, 386 HibernateSessionFactory.HIBERNATE_3_PUBLIC_ID, 387 HibernateSessionFactory.HIBERNATE_3_SYSTEM_ID, 388 HibernateSessionFactory.HIBERNATE_3_CHARSET.name()), HibernateSessionFactory.HIBERNATE_3_CHARSET, false); 389 } 390 catch(Exception e){} 391 } 392 } 393 } 394 395 private static long loadMapping(StringBuilder sb,ORMConfiguration ormConf, Component cfc) { 396 397 Resource res=cfc.getPageSource().getResource(); 398 if(res!=null){ 399 res=res.getParentResource().getRealResource(res.getName()+".hbm.xml"); 400 try{ 401 sb.append(CommonUtil.toString(res, CommonUtil.UTF8)); 402 return res.lastModified(); 403 } 404 catch(Exception e){} 405 } 406 return 0; 407 } 408 409 @Override 410 public int getMode() { 411 //MUST impl 412 return MODE_LAZY; 413 } 414 415 @Override 416 public String getLabel() { 417 return "Hibernate"; 418 } 419 420 421 422 423 424 @Override 425 public ORMConfiguration getConfiguration(PageContext pc) { 426 ApplicationContext ac = pc.getApplicationContext(); 427 if(!ac.isORMEnabled()) 428 return null; 429 return ac.getORMConfiguration(); 430 } 431 432 /** 433 * @param pc 434 * @param session 435 * @param entityName name of the entity to get 436 * @param unique create a unique version that can be manipulated 437 * @param init call the nit method of the cfc or not 438 * @return 439 * @throws PageException 440 */ 441 public Component create(PageContext pc, HibernateORMSession session,String entityName, boolean unique) throws PageException { 442 SessionFactoryData data = session.getSessionFactoryData(); 443 // get existing entity 444 Component cfc = _create(pc,entityName,unique,data); 445 if(cfc!=null)return cfc; 446 447 SessionFactoryData oldData = getSessionFactoryData(pc, INIT_NOTHING); 448 Map<Key, SessionFactory> oldFactories = oldData.getFactories(); 449 SessionFactoryData newData = getSessionFactoryData(pc, INIT_CFCS); 450 Map<Key, SessionFactory> newFactories = newData.getFactories(); 451 452 Iterator<Entry<Key, SessionFactory>> it = oldFactories.entrySet().iterator(); 453 Entry<Key, SessionFactory> e; 454 SessionFactory newSF; 455 while(it.hasNext()){ 456 e = it.next(); 457 newSF = newFactories.get(e.getKey()); 458 if(e.getValue()!=newSF){ 459 session.resetSession(pc,newSF,e.getKey(),oldData); 460 cfc = _create(pc,entityName,unique,data); 461 if(cfc!=null)return cfc; 462 } 463 } 464 465 466 467 ORMConfiguration ormConf = pc.getApplicationContext().getORMConfiguration(); 468 Resource[] locations = ormConf.getCfcLocations(); 469 470 throw ExceptionUtil.createException(data,null, 471 "No entity (persitent component) with name ["+entityName+"] found, available entities are ["+ListUtil.listToList(data.getEntityNames(), ", ")+"] ", 472 "component are searched in the following directories ["+toString(locations)+"]"); 473 474 } 475 476 477 private String toString(Resource[] locations) { 478 if(locations==null) return ""; 479 StringBuilder sb=new StringBuilder(); 480 for(int i=0;i<locations.length;i++){ 481 if(i>0) sb.append(", "); 482 sb.append(locations[i].getAbsolutePath()); 483 } 484 return sb.toString(); 485 } 486 487 private static Component _create(PageContext pc, String entityName, boolean unique, SessionFactoryData data) throws PageException { 488 CFCInfo info = data.getCFC(entityName, null); 489 if(info!=null) { 490 Component cfc = info.getCFC(); 491 if(unique){ 492 cfc=(Component)cfc.duplicate(false); 493 if(cfc.contains(pc,CommonUtil.INIT))cfc.call(pc, "init",new Object[]{}); 494 } 495 return cfc; 496 } 497 return null; 498 } 499} 500class CFCInfo { 501 private String xml; 502 private long modified; 503 private Component cfc; 504 private DataSource ds; 505 506 public CFCInfo(long modified, String xml, Component cfc, DataSource ds) { 507 this.modified=modified; 508 this.xml=xml; 509 this.cfc=cfc; 510 this.ds=ds; 511 } 512 /** 513 * @return the cfc 514 */ 515 public Component getCFC() { 516 return cfc; 517 } 518 /** 519 * @return the xml 520 */ 521 public String getXML() { 522 return xml; 523 } 524 /** 525 * @return the modified 526 */ 527 public long getModified() { 528 return modified; 529 } 530 531 public DataSource getDataSource() { 532 return ds; 533 } 534 535} 536